题解:P11802 【MX-X9-T6】『GROI-R3』Graph

· · 题解

模拟赛赛时认为 \gcd 必须等于 1,爆零了/ll。

看来考试时要把发现结论的依据写一写(周期结论)。

首先由题意,G 上任意一个点能走到他自己,因此 G 是若干环的并。

得到 \gcd=1 的错误结论是由于周期结论的直觉,让我们仔细想想这个结论能带给我们什么信息。

对于 G 上一个长为 len 的环,我们从一个点出发,每次走 k 步,能得到 \gcd(k,len) 个长为 \frac{len}{\gcd(k,len)} 的环。

因此,我们还可以选择把若干个相同长度的环在 G 中拼在一起

具体地,用若干个长度为 i 的环拼成长为 i\times t 的大环,则有:

\frac{it}{\gcd(k,len)}=i \\ t=\gcd(k,it) \\ \gcd(\frac{k}{t},i)=1

因此,设 cntloop_i 表示环长为 i 的环数,找到最小的 t 使得 \gcd(\frac{k}{t},i)=1,则 t|cntloop_i。这个条件是充要的。

考虑计数 DP。

按链 id DP要记录每个环长的 cnt,不可接受。

因此考虑按需要记录的值 DP,这样的好处是我们可以在进行 i\to i+1 的转移时顺便判断 i 是否合法。

f_{i,j,k} 表示当前处理长度 i,长度为 i 的链有 j 个,有 k 个剩余孤点(记录长度 \leq j 的链个数不容易保证不重不漏,因此选择 = 的定义)。

有一个技巧:我们可以把没有闭合的链各加上一个孤点,以保证 i 增加时链长 =i 的定义。

然而 w 个孤点在环上的贡献为 w!,这要求了我们不能实时计算这个贡献,只能在最后计算。

具体地,我们在转移时不区分孤点,最后乘上孤点个数的阶乘,根据组合意义,这个方法在不存在全是孤点组成的环时正确。

在孤点成环时,我们发现是阶乘和圆排列的区别。

具体地,对于一个 x,设长为 x 的全孤点环为 y 个,则我们多算了 x^y y! 倍,因为我们要:

我们尝试用分步的思想来推导 DP 转移方程。

记孤点个数为 cnt1,答案即为 f_{n+1,0,0}\times cnt1!

分析复杂度,有 ij\leq n,ic\leq k\leq n,l\leq j

总复杂度 $O(n\sum\frac{n^3}{i^3})=O(n^4\sum\frac{1}{i^3})=O(n^4)$。 在推导时,我们发现没有转移条件时完全可以把【选出若干孤点构成环】和【将已有的链闭合形成环】分步转移做到 $O(n^3)$。 把条件改成 $-cntloop_i-l=c\pmod t$ 仍然可以分步转移,枚举 $cntloop_i+l\bmod t$ 的值做若干遍即可。 $O(n^3)$ 卡卡就过 $n=1000$ 了(逃。 ```cpp #include<bits/stdc++.h> using namespace std; #define ll long long const ll mod=998244353; ll ksm(ll a,ll b){ ll res=1; while(b){ if(b&1) res=res*a%mod; a=a*a%mod; b/=2; } return res; } int n,k,a[1005],vis[1005],in[1005]; int C[1005][1005],fac[1005]; int f[2][1005][1005]; int f1[1005][1005]; int f2[1005][1005]; int getmint(int i){ int ki=k; for(int j=1;j*j<=k;j++){ if(j!=1&&i%j==0){ while(ki%j==0) ki/=j; } if(k/j!=1&&i%(k/j)==0){ while(ki%(k/j)==0) ki/=(k/j); } } return k/ki; } int cntloop[1005],cntlink[1005],cnt1; void upd(int &x,int y){ x=(x+y)%mod; } int inv[1005][1005]; int f1x[1005][1005]; void solve(){ cin>>n>>k; for(int i=1;i<=n;i++) in[i]=vis[i]=cntloop[i]=cntlink[i]=0; for(int i=0;i<=n;i++) for(int j=0;j<=n;j++) f[0][i][j]=f[1][i][j]=f1[i][j]=f1x[i][j]=f2[i][j]=0; for(int i=1;i<=n;i++) cin>>a[i]; for(int i=1;i<=n;i++) if(a[i]!=-1) in[a[i]]=1; cnt1=0; for(int i=1;i<=n;i++){ if(vis[i]||in[i]) continue; int now=i,sz=1; vis[now]=1; while(1){ if(a[now]==-1) break; now=a[now]; vis[now]=1; sz++; } if(sz==1&&a[i]==-1){ cnt1++; continue; } cntlink[sz]++; } for(int i=1;i<=n;i++){ if(vis[i]) continue; int now=i,sz=1; vis[now]=1; while(1){ if(a[now]==-1) break; now=a[now]; if(vis[now]){ break; } vis[now]=1; sz++; } cntloop[sz]++; } f[1][0][cnt1]=1; for(int i=1;i<=n;i++){ int t=getmint(i); int now=i&1; int nxt=1-now; for(int j=0;i*j<=n;j++) for(int k=0;k<=cnt1;k++){ f[nxt][j][k]=0; f1[j][k]=f1x[j][k]=f2[j][k]=0; } for(int j=0;i*j<=n&&j+cntlink[i]<=n;j++){ for(int k=0;k<=cnt1;k++){ upd(f1[j+cntlink[i]][k],f[now][j][k]); } } for(int mdt=0;mdt<t;mdt++){ if(i*mdt>cnt1) continue; for(int j=0;i*j<=n;j++){ for(int k=i*mdt;k<=cnt1;k++){ if(!f1[j][k]) continue; for(int c=mdt;i*c<=k;c+=t){ upd(f1x[j][k-i*c],1ll*f1[j][k]*inv[i][c]%mod); } } } for(int j=0;i*j<=n;j++){ for(int k=0;k<=cnt1-i*mdt;k++){ if(!f1x[j][k]) continue; for(int l=((t-cntloop[i]-mdt)%t+t)%t;l<=j;l+=t){ upd(f2[j-l][k],1ll*f1x[j][k]%mod*C[j][l]%mod); } f1x[j][k]=0; } } } for(int j=0;i*j<=n;j++){ for(int k=j;k<=cnt1;k++){ upd(f[nxt][j][k-j],f2[j][k]); } } } cout<<1ll*f[(n+1)&1][0][0]*fac[cnt1]%mod<<"\n"; } signed main(){ ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); C[0][0]=1; fac[0]=1; for(int i=1;i<=1000;i++){ fac[i]=1ll*fac[i-1]*i%mod; C[i][0]=C[i][i]=1; for(int j=1;j<i;j++) C[i][j]=(C[i-1][j]+C[i-1][j-1])%mod; } for(int i=0;i<=1000;i++){ for(int j=0;j<=1000;j++){ inv[i][j]=ksm(1ll*ksm(i,j)*fac[j]%mod,mod-2); } } solve(); } ```