「ARC140D」One to One - 题解

题意

题目链接:Link

你有一个长度为 \(n\) 的序列 \(a_1,a_2,\dots,a_n\),其中每个元素都是 \([1,n]\) 中的整数。

初始时有编号为 \(1 \sim n\)\(n\) 个节点,对于每个 \(1\leq i \leq n\),从 \(i\)\(a_i\) 连一条无向边。\(a_i=-1\) 表示 \(a_i\) 还没有确定。你需要对所有可能的 \(a\) 序列求出图中连通块数量的和对 \(998244353\) 取模的结果。

题解

若对每一块进行考虑,那么对于一个有 \(n\) 个点 \(n\) 条边的块,也就是基环树或环来说,里面一定不会存在 \(a_i=-1\)。否则就是一棵树了,那么也最多只会有一个 \(a_i=-1\)

这意味着 $a_i =-1 $ 所在的连通块一定是树,剩下的一定是环或基环树。且这些环或者基环树的贡献一定不会变,可以先加上。

于是只要考虑是树的块。首先如果树中的 \(-1\) 连向了一个环或者基环树,那么当前情况的贡献 \(f\) 是不变的。不考虑。所以只考虑树和树之间。

一棵树也只有一个 \(-1\),因此考虑将一个树看成一个点。于是设 \(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;
}
posted @ 2022-10-24 20:29  Rainy7  阅读(102)  评论(0编辑  收藏  举报