【CF772C】Vulnerable Kerbals

题目

题目链接:https://codeforces.com/problemset/problem/772/C
给出 \(m,n\),再给一个 \(m\) 个数的集合让你构造一个序列满足以下的条件:

  1. 这个序列的所有数都在 \(0\sim m-1\) 之间。
  2. 这个数列的所有前缀积取模 \(m\) 都不同。
  3. 所有的前缀积取模 \(m\) 都不能出现在给你的集合中。
  4. 最大化这个序列的长度。

输出任意满足条件的序列。
\(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;
}
posted @ 2021-10-26 09:35  stoorz  阅读(68)  评论(0编辑  收藏  举报