题意:给出m和n个在[0,m)的数字,要求构造一个最长的序列满足
1.序列中的数位[0,m)
2.序列的前缀积对m取模后不能重复
3.序列的前缀积对m取模后不能与n个数字相同
题解:
考虑前缀积为x,下一个数取a,x*a%m=y,则gcd(y,m)必然是gcd(x,m)的倍数,那么我们构造出序列后的前缀积取模序列是一个递增序列,且后面数必然是前面数的倍数
构造一个以前缀积取模后的值为点的图,把可以取的前缀积积取模点按与m的gcd相同的缩成一个点,然后向与m的gcd为当前点倍数的点连一条边,边的权值为当前点集的大小,这代表我们可以构造一段序列,使前缀积取模后的值为当前点集中的点,然后再dp求最长路
构造序列的时候用exgcd就行,exgcd求解的是形如这样的问题:ax+by=gcd(a,b)
#include<cstdio>
#include<vector>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=2e5+10;
#define PB push_back
#define ll long long
vector<int>ans;
vector<int>G[N],b[N];
int dp[N],pre[N];
bool vis[N];
int gcd(int x,int y)
{
return y==0?x:gcd(y,x%y);
}
ll exgcd(ll a,ll b,ll &x,ll &y)
{
if(b==0)
{
x=1;
y=0;
return a;
}
ll gcd=exgcd(b,a%b,x,y);
ll tmp=x;
x=y;
y=tmp-a/b*x;
return gcd;
}
int main()
{
vis[0]=0;
int n,m;
scanf("%d%d",&n,&m);
while(n--){
int x;scanf("%d",&x);
vis[x]=1;
}
for(int i=1;i<m;i++)
if(!vis[i])
b[gcd(i,m)].PB(i);
for(int i=1;i<m;i++)
for(int j=i+i;j<m;j+=i)
G[i].PB(j);
int t=0;
for(int i=1;i<m;i++){
int h=b[i].size();
dp[i]=max(h,dp[i]);
for(int j=0;j<G[i].size();j++){
int v=G[i][j];
int tmp=dp[i]+b[v].size();
if(dp[v]<tmp){
dp[v]=tmp;
pre[v]=i;
}
}
if(dp[i]>dp[t])
t=i;
}
while(t){
for(int i=0;i<b[t].size();i++)
ans.PB(b[t][i]);
t=pre[t];
}
printf("%d\n",ans.size()+(!vis[0]));
reverse(ans.begin(),ans.end());
if(!ans.empty()){
ll a=ans[0];
printf("%d",ans[0]);
for(int i=1;i<ans.size();i++){
ll x=0,y=0;
ll b=m;
int g=exgcd(a,b,x,y);
x*=(ans[i]/g);
x=(x%m+m)%m;
printf(" %d",x);
a=a*x%m;
}
}
if(!vis[0])
puts(" 0");
else puts("");
}