【CF772C】Vulnerable Kerbals
题目
题目链接:https://codeforces.com/problemset/problem/772/C
给出 \(m,n\),再给一个 \(m\) 个数的集合让你构造一个序列满足以下的条件:
- 这个序列的所有数都在 \(0\sim m-1\) 之间。
- 这个数列的所有前缀积取模 \(m\) 都不同。
- 所有的前缀积取模 \(m\) 都不能出现在给你的集合中。
- 最大化这个序列的长度。
输出任意满足条件的序列。
\(n\leq 2\times 10^5\)。
思路
首先如果 \(0\) 没有出现在集合中,那么一定会放在序列的最后一位,接下来考虑 \(1\sim 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(m\log 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])<<"\n";
print(n);
if (!used[0]) putchar(48);
return 0;
}