题目
题目链接:https://codeforces.com/problemset/problem/772/C
给出 (m,n),再给一个 (m) 个数的集合让你构造一个序列满足以下的条件:
- 这个序列的所有数都在 (0sim m-1) 之间。
- 这个数列的所有前缀积取模 (m) 都不同。
- 所有的前缀积取模 (m) 都不能出现在给你的集合中。
- 最大化这个序列的长度。
输出任意满足条件的序列。
(nleq 2 imes 10^5)。
思路
首先如果 (0) 没有出现在集合中,那么一定会放在序列的最后一位,接下来考虑 (1sim m-1)。
根据裴蜀定理,若前缀积为 (x),那么再添加一个数字进去,前缀积只可能是 (gcd(x,m)) 的倍数。
一个 (O(m^2)) 的做法是如果 (gcd(x,m)|y),那么就从 (x) 向 (y) 连一条边。然后在不经过集合内数字的前提下,最大化路径长度。
观察到其实有很多数字下一步能到达的点是重复的。考虑吧所有 (gcd(x,m)) 相同的点缩起来,这样一个新点 (d) 表示 (gcd(x,m)=d) 的所有数,显然这些数字是可以互相到达的,所以点 (d) 的权值就是满足条件的 (x) 的数量。然后点 (d) 只需要向 (d) 的倍数连边就行了。
这样的话图就被缩成了一张 DAG。直接跑 dp,记录前驱,最后 exgcd 求一下序列就好了。
时间复杂度 (O(mlog m))。
代码
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=200010;
int n,m,cnt[N],f[N],pre[N];
bool used[N];
vector<int> d[N];
int exgcd(int a,int b,int &x,int &y)
{
if (!b) { x=1; y=0; return a; }
int d=exgcd(b,a%b,x,y),t=y;
y=x-(a/b)*y; x=t;
return d;
}
int print(int n)
{
if (n==-1) return 1;
int a=print(pre[n]);
for (int i=0;i<(int)d[n].size();i++)
{
int b=d[n][i],x,y,d=exgcd(a,m,x,y);
cout<<((b/d*x)%m+m)%m<<" "; a=b;
}
return a;
}
signed main()
{
scanf("%lld%lld",&n,&m);
for (int i=1,x;i<=n;i++)
scanf("%lld",&x),used[x]=1;
for (int i=1,x,y;i<m;i++)
if (!used[i])
{
int k=exgcd(i,m,x,y);
cnt[k]++; d[k].push_back(i);
}
memset(pre,-1,sizeof(pre));
for (int i=1;i<m;i++)
{
f[i]+=cnt[i];
for (int j=i*2;j<m;j+=i)
if (f[j]<f[i]) f[j]=f[i],pre[j]=i;
}
n=1;
for (int i=1;i<m;i++)
if (f[i]>f[n]) n=i;
cout<<f[n]+(!used[0])<<"
";
print(n);
if (!used[0]) putchar(48);
return 0;
}