题意:
给出一个图求最小生成树的个数
分析:
生成树计数可以使用Matrix-Tree定理解决,本题最主要的区别是有了一个最小生成树的额外条件。
首先考虑一下如何得到最小生成树。
Kruskal算法的基本思想是,按照边长排序,然后不断将短边加入集合,最终一步如果能成功把n-1条边都加入同一个集合,则找到了最小生成树。在维护集合时,可以使用并查集来快速处理。
如果把Kruskal的过程按照边长划分成多个阶段,实际上是处理了所有短边的连通性之后继续处理下一个长度的边的连通性,并依次继续处理剩下边的连通性。然后我们可以发现,不同长度的边之间的连通性互不影响!!!
假设存在n1条长度为c1的边,n2条长度为c2的边...则Kruskal首先处理c1边的连通性,然后处理c2边的连通性,对于c1边的连通性的处理可能有多种方案,即从n1条边中取出一定数量的边构成最大连通图,但是最终处理完之后的结果对于c2来说是完全一样的。因此算法就出来了,在Kruskal的基础上,使用Matrix-Tree定理处理每个阶段生成树的种数,最后将所有阶段的结果相乘即可。
具体实现为:
在Kruskal的基础上,每完成一个阶段(检查完一个长度),就将所有遍历过的点缩成一个点,然后用Matrix-Tree定理计算该点与下一组点组成的连通图中生成树的个数。最终把每一个阶段的结果相乘即可。
参考博客:
https://www.cnblogs.com/jcf94/p/4071098.html
https://www.cnblogs.com/flipped/p/5769228.html
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=105;
const int maxm=1005;
ll n,m,mod,ans;
vector<int> gra[maxn];
struct edge{
int u,v,w;
}e[maxm];
bool cmp(edge a,edge b){
return a.w<b.w;
}
ll mat[maxn][maxn]; //矩阵
ll g[maxn][maxn]; //邻接矩阵
int fa[maxn],ka[maxn]; //ka压缩路径,fa缩点。
bool vis[maxn];
ll gs(ll a[][maxn],int S){
S--; //单纯计算行列式不要该行
for(int i=1;i<=S;i++){
for(int j=1;j<=S;j++){
a[i][j]=(a[i][j]+mod)%mod;
}
}
long long ans=1;
for(int j=1;j<=S;j++){
for(int i=j+1;i<=S;i++){
while(a[i][j]){
long long t=a[j][j]/a[i][j];
for(int k=j;k<=S;k++){
a[j][k]=(a[j][k]-t*a[i][k]%mod+mod)%mod;
swap(a[i][k],a[j][k]);
}
ans*=-1;
}
}
if(a[j][j]==0) return 0;
ans=ans*a[j][j]%mod;
}
return (ans+mod)%mod;
}
int find(int a,int f[]){
return f[a]==a?a:f[a]=find(f[a],f);
}
void matrix_tree(){//对当前长度的边连接的每个联通块计算生成树个数
for(int i=1;i<=n;i++)if(vis[i]){
gra[find(i,ka)].push_back(i);//将i节点压入所属的联通块
vis[i]=0;//一边清空vis数组
}
for(int i=1;i<=n;i++){
if(gra[i].size()>1){//联通块的点数为1时生成树数量是1
memset(mat,0,sizeof mat);//清空矩阵
int len=gra[i].size();
for(int j=0;j<len;j++){
for(int k=j+1;k<len;k++){//构造这个联通块的矩阵(有重边)
int u=gra[i][j],v=gra[i][k];
if(g[u][v]){
mat[k][j]=(mat[j][k]-=g[u][v]);
mat[k][k]+=g[u][v];
mat[j][j]+=g[u][v];
}
}
}
ans=ans*gs(mat,gra[i].size())%mod;
for(int j=0;j<len;j++) fa[gra[i][j]]=i;//缩点
}
}
for(int i=1;i<=n;i++){
gra[i].clear();
ka[i]=fa[i]=find(i,fa);
}
}
int main(){
while(scanf("%lld%lld%lld",&n,&m,&mod),n){
for(int i=0;i<m;i++){
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
e[i]=edge{u,v,w};
}
sort(e,e+m,cmp);
memset(g,0,sizeof g);
ans=1;
for(int i=1;i<=n;i++) ka[i]=fa[i]=i;
for(int i=0;i<=m;i++){//边从小到大加入
if(i&&e[i].w!=e[i-1].w||i==m){//处理完长度为e[i-1].w的所有边
matrix_tree();//计算生成树。
}
int u=find(e[i].u,fa),v=find(e[i].v,fa);//连的两个缩点后的点
if(u!=v){//如果不是一个
vis[v]=vis[u]=1;
ka[find(u,ka)]=find(v,ka);//两个分量在一个连通块。
g[u][v]++;g[v][u]++;//邻接矩阵
}
}
bool flag=true;
for(int i=2;i<=n;i++){
if(fa[i]!=fa[i-1]) flag=0;
}
printf("%lld\n",flag?ans%mod:0);//注意mod可能为1,这样m=0时如果ans不%mod就会输出1
}
return 0;
}