题解
若对每一块进行考虑,那么对于一个有 \(n\) 个点 \(n\) 条边的块,也就是基环树或环来说,里面一定不会存在 \(a_i=-1\)。否则就是一棵树了,那么也最多只会有一个 \(a_i=-1\)。
这意味着 $a_i =1 $ 所在的连通块一定是树,剩下的一定是环或基环树。且这些环或者基环树的贡献一定不会变,可以先加上。
于是只要考虑是树的块。首先如果树中的 \(-1\) 连向了一个环或者基环树,那么当前情况的贡献 \(f\) 是不变的。不考虑。所以只考虑树和树之间。于是设 \(g(j)\) 表示所有树合并后只剩下 \(j\) 个连通块的方案数。
转移显然有 \(g(j) \leftarrow g(j-1) \times sz_i\)。因为点 \(i\) 都是内部连边,会贡献一个新的连通块。最后算一下贡献就好了。
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
using namespace std;
const int N=2000+5;
const int Mod=998244353;
struct edge{
int v,nx;
}e[N<<1];
int n,ans,ne,cnt,f[N],a[N],pw[N],mul[N],sz[N],v[N],g[N];
bool vis[N];
void read(int u,int v)
{ e[++ne].v=v;
e[ne].nx=f[u];
f[u]=ne;
}
void dfs(int u)
{ vis[u]=1,sz[u]=1;
for(int i=f[u];i;i=e[i].nx)
{ int v=e[i].v;
if(!vis[v])dfs(v),sz[u]+=sz[v];
}
}
int main()
{ freopen("sum.in","r",stdin);
freopen("sum.out","w",stdout);
scanf("%d",&n);
for(int i=pw[0]=mul[0]=1;i<=n;i++)mul[i]=1ll*mul[i-1]*i%Mod,pw[i]=1ll*pw[i-1]*n%Mod;
for(int i=1;i<=n;i++)
{ scanf("%d",&a[i]);
if(a[i]!=-1)read(i,a[i]),read(a[i],i);
}
for(int i=1;i<=n;i++)
if(a[i]==-1)dfs(i),v[++cnt]=sz[i];
for(int i=1;i<=n;i++)
if(!vis[i])dfs(i),ans=(ans+pw[cnt])%Mod;
g[0]=1;
for(int i=1;i<=cnt;i++)
for(int j=i-1;j>=0;j--)g[j+1]=(g[j+1]+1ll*g[j]*v[i]%Mod)%Mod;
for(int i=1;i<=cnt;i++)ans=(ans+1ll*g[i]*mul[i-1]%Mod*pw[cnt-i]%Mod)%Mod;
printf("%d\n",ans);
return 0;
}
标签:连通,int,题解,基环树,include,ARC140D,Mod
From: https://www.cnblogs.com/Rainy7/p/solution-at-arc140d.html