题目大意
给定一个 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
",1%p);
return 0;
}
int ans=poww(n,k-2);
for(i=1;i<=k;i++) ans=ans*s[i]%p;
printf("%lld
",ans);
return 0;
}