[CF156D] Clues

题目大意

给定一个 n 个点 m 条边的带标号无向图,它有 k 个连通块,求添加 k-1 条边使得整个图连通的方案数,答案对 p 取模。

解析

考虑将k个连通块看作是k个点。我们枚举每个连通块的度数,由Prufer序列的结论,我们可以得到总方案数为:

\[\frac{(k-2)!}{\prod_{i=1}^{k}(d_i-1)!}\prod_{i=1}^{k}s_i^{d_i} \]

其中\(d\)满足\(d_i>0\)\(\sum_{i=1}^kd_i=2k-2\)\(s_i\)是第\(i\)个连通块的大小。

关于二项式定理有一个推论:

\[(x_1+x_2+...+x_m)^n=\frac{n!}{\prod_{i=1}^{m}c_i!}\prod_{i=1}^m x_i^{c_i}\ \ (\sum_{i=1}^mc_i=n) \]

证明比较显然,依次使用一般二项式定理即可。

因此,最后的答案为\((s_1+s_2+...+s_k)^{k-2}\prod_{i=1}^k s_i\)

代码

#include <iostream>
#include <cstdio>
#define int long long
#define N 100002
using namespace std;
int head[N],ver[N*2],nxt[N*2],l;
int n,m,p,k,i,s[N];
bool vis[N];
int read()
{
	char c=getchar();
	int w=0;
	while(c<'0'||c>'9') c=getchar();
	while(c<='9'&&c>='0'){
		w=w*10+c-'0';
		c=getchar();
	}
	return w;
}
void insert(int x,int y)
{
	l++;
	ver[l]=y;
	nxt[l]=head[x];
	head[x]=l;
}
void dfs(int x,int id)
{
	vis[x]=1;s[id]++;
	for(int i=head[x];i;i=nxt[i]){
		int y=ver[i];
		if(!vis[y]) dfs(y,id);
	}
}
int poww(int a,int b)
{
	int ans=1,base=a;
	while(b){
		if(b&1) ans=ans*base%p;
		base=base*base%p;
		b>>=1;
	}
	return ans;
}
signed main()
{
	n=read();m=read();p=read();
	for(i=1;i<=m;i++){
		int u=read(),v=read();
		insert(u,v);insert(v,u);
	}
	for(i=1;i<=n;i++){
		if(!vis[i]) k++,dfs(i,k);
	}
	if(k==1){
		printf("%lld\n",1%p);
		return 0;
	}
	int ans=poww(n,k-2);
	for(i=1;i<=k;i++) ans=ans*s[i]%p;
	printf("%lld\n",ans);
	return 0;
}
posted @ 2020-08-13 22:02  CJlzf  阅读(110)  评论(0编辑  收藏  举报