CF800 C

题意:
在模m的意义下,ban掉n个数。构造一个最长的数列,使得:
1、前缀之积两两不等
2、前缀之积不能出现n个被ban的值
n< m<=200000

#include
#include
#include
#include
#include
#include
#define N 410000
#define pb push_back
using namespace std;
struct node{int x,y,nex;}a[20*N];
int n,m,len,fir[N],f[N],g[N],sta[N],belong[N],tp,dfn[N],low[N],id,cnt,du[N],e[N*20][2];
int p[N*20],tail,ans,w,A[N],res[N],num,fr[N];
bool b[N],insta[N];
vector<int> v[N];
void ins(int x,int y)
{
    a[++len].x=x;a[len].y=y;a[len].nex=fir[x];fir[x]=len;
}
int gcd(int a,int b)
{
    if(b==0) return a;
    return gcd(b,a%b);
}
void tarjan(int x,int fa)
{
    dfn[x]=low[x]=++id;
    sta[++tp]=x;insta[x]=1;
    for(int k=fir[x];k;k=a[k].nex)
    {
        int y=a[k].y;
        if(dfn[y]==0) {tarjan(y,x);low[x]=min(low[x],low[y]);}
        else if(insta[y]) low[x]=min(low[x],dfn[y]);
    }
    if(low[x]>dfn[fa])
    {
        cnt++;
        int y;
        do{
            y=sta[tp];sta[tp--]=0;
            insta[y]=0;
            belong[y]=cnt;
            g[cnt]+=b[y];
            v[cnt].pb(y);
        }while(y!=x);
        int o=1;
    }
}
void tpsort()
{
    for(int i=1;i<=cnt;i++) if(du[i]==0) f[i]=g[i],p[++tail]=i;
    for(int i=1;i<=tail;i++)
    {
        int x=p[i];
        if(f[x]>ans) {ans=f[x];w=x;}
        for(int k=fir[x];k;k=a[k].nex)
        {
            int y=a[k].y;
            du[y]--;
            if(f[x]+g[y]>f[y]) {f[y]=f[x]+g[y];fr[y]=x;}
            if(du[y]==0) p[++tail]=y;
        }
    }
}
int exgcd(int a,int b,int &x,int &y)
{
    if(b==0) {x=1;y=0;return a;}
    int t=a/b,xx,yy,g;
    g=exgcd(b,a%b,xx,yy);
    x=yy;y=xx-t*yy;
    return g;
}
void make_ans()
{
    int x=w;
    while(x)
    {
        int siz=v[x].size();
        for(int i=0;iif(b[v[x][i]]) A[++num]=v[x][i];
        x=fr[x];
    }
    res[1]=A[1];
    for(int i=2;i<=num;i++)
    {
        int x,y,g;g=exgcd(A[i-1],m,x,y);
        x=(x%m+m)%m;
        x=1ll*x*(A[i]/g)%m;
        res[i]=x;
    }
    if(b[0]) res[++num]=0;
    printf("%d\n",num);
    for(int i=1;i<=num;i++) printf("%d ",res[i]);
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=0;i1;
    for(int i=1;i<=n;i++)
    {
        int x;scanf("%d",&x);
        b[x]=0;
    }
    for(int i=1;iint g=gcd(i,m);
        ins(i,g+m);
    }
    for(int i=1;ifor(int j=i;jfor(int i=1;iif(dfn[i]==0) tarjan(1,0);
    for(int i=1;i<=len;i++) e[i][0]=a[i].x,e[i][1]=a[i].y;
    for(int i=1;i<=2*m;i++) fir[i]=0;
    int tmp=len;len=0;
    for(int i=1;i<=tmp;i++) 
    {
        int x=belong[e[i][0]],y=belong[e[i][1]];
        if(x!=y) {ins(y,x);du[x]++;}
    }
    tpsort();
    make_ans();
    return 0;
}

题解:
直接做一个前缀积的数列,变回原数列是容易的
在能一次转移到的数字间连边,缩点后拓补图dp一下就可以了
直接连边是 O(m2)
考虑 xai=ai+1(%m) 有解的条件是 gcd(ai,m)|ai+1
那么对于一个数字i,连上 i>gcd(i,m) d>i(d|i) 这些边就可以了
复杂度 O(mlogm)
现场做的时候一直在想原根,按二的幂分组什么的。。先入为主了,实在不应该

你可能感兴趣的:(codeforces,数论)