专栏文章
题解:P11802 【MX-X9-T6】『GROI-R3』Graph
P11802题解参与者 1已保存评论 0
文章操作
快速查看文章及其快照的属性,并进行相关操作。
- 当前评论
- 0 条
- 当前快照
- 1 份
- 快照标识符
- @min9vsa3
- 此快照首次捕获于
- 2025/12/01 22:56 3 个月前
- 此快照最后确认于
- 2025/12/01 22:56 3 个月前
模拟赛赛时认为 必须等于 ,爆零了/ll。
看来考试时要把发现结论的依据写一写(周期结论)。
首先由题意, 上任意一个点能走到他自己,因此 是若干环的并。
得到 的错误结论是由于周期结论的直觉,让我们仔细想想这个结论能带给我们什么信息。
对于 上一个长为 的环,我们从一个点出发,每次走 步,能得到 个长为 的环。
因此,我们还可以选择把若干个相同长度的环在 中拼在一起。
具体地,用若干个长度为 的环拼成长为 的大环,则有:
因此,设 表示环长为 的环数,找到最小的 使得 ,则 。这个条件是充要的。
考虑计数 DP。
按链 id DP要记录每个环长的 ,不可接受。
因此考虑按需要记录的值 DP,这样的好处是我们可以在进行 的转移时顺便判断 是否合法。
令 表示当前处理长度 ,长度为 的链有 个,有 个剩余孤点(记录长度 的链个数不容易保证不重不漏,因此选择 的定义)。
有一个技巧:我们可以把没有闭合的链各加上一个孤点,以保证 增加时链长 的定义。
然而 个孤点在环上的贡献为 ,这要求了我们不能实时计算这个贡献,只能在最后计算。
具体地,我们在转移时不区分孤点,最后乘上孤点个数的阶乘,根据组合意义,这个方法在不存在全是孤点组成的环时正确。
在孤点成环时,我们发现是阶乘和圆排列的区别。
具体地,对于一个 ,设长为 的全孤点环为 个,则我们多算了 倍,因为我们要:
- 除掉圆排列的循环。
- 当孤点都相同时,这些环没有顺序之分,除掉【最后乘上孤点个数的阶乘】对这些环顺序的错误贡献。
我们尝试用分步的思想来推导 DP 转移方程。
-
加入原图上的长为 的链,这一步是模拟题意。( 指原图上长为 的链个数)。
-
求出最小的 使得 ,加入一些环,满足长为 的总环数是 的倍数,形成环有如下两种方式:
- 选出若干孤点构成环。
- 将已有的链闭合形成环。
即枚举 ,,转移条件是 。 -
把没有闭合的链加上一个孤点,。
记孤点个数为 ,答案即为 。
分析复杂度,有 。
各贡献 , 贡献 。
总复杂度 。
在推导时,我们发现没有转移条件时完全可以把【选出若干孤点构成环】和【将已有的链闭合形成环】分步转移做到 。
把条件改成 仍然可以分步转移,枚举 的值做若干遍即可。
卡卡就过 了(逃。
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();
}
相关推荐
评论
共 0 条评论,欢迎与作者交流。
正在加载评论...