最小生成树计数
Posted fangbozhen
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了最小生成树计数相关的知识,希望对你有一定的参考价值。
题目描述
给出一张图,求它最小生成树的个数。
思路
这道题不论是暴力还是矩阵树定理都需要一个定理:同一个图中的所有最小生成树的边权的数量都一定。
证明:假设定理不成立,那我们必定可以有两条最小生成树边a、b和非树边x、y,满足权值a+b=x+y,并且删除a、b再加入x、y后仍是一棵最小生成树。我们不妨假设v[x]<v[y],v[a]<v[b],那么删除两条边后会形成三个连通支,而由于v[x]!=v[a],v[y]!=v[b],所以必定会有x连接T1、T2并且小于连接这两连通支的树边,或a连接T1、T2并且小于连接这两连通支的非树边。这两种情况都会使另一个最小生成树并不是最小,因为可以替换为更小的边。
有了这个定理后,我们先考虑暴力。我们可以先做一次Kruskal求出最小生成树并统计每个权值边的数量,接下来我们对每一种边权(从小到大顺序),对于某种边权,我们暴力dfs枚举选和不选两种状态(因为同种边权数量较少),用并查集盘连通性,不过需要注意为了快速实现连通块的分离,我们不能使用路径压缩。乘法原理统计答案即可。
对于同种边权较多的情况,我们选用矩阵树定理。(待我学习一下)
代码
#include <bits/stdc++.h> using namespace std; const int MAXN=110,MAXM=1010,mod=31011; struct Edge { int x,y,v; }e[MAXM]; struct aa { int l,r,cnt; }a[MAXM]; int fa[MAXN],sum; bool cmp(Edge x,Edge y) { return x.v<y.v; } int find(int x) { return fa[x]==x?x:find(fa[x]); } void dfs(int u,int k,int x) { // cout<<u<<‘ ‘<<k<<‘ ‘<<x<<‘ ‘<<a[x].r<<endl; if(u==a[x].r+1) { if(k==a[x].cnt)sum++; return ; } int fx=find(e[u].x),fy=find(e[u].y); if(fx!=fy) { fa[fx]=fy; dfs(u+1,k+1,x); fa[fx]=fx;fa[fy]=fy; } dfs(u+1,k,x); } int main() { int n,m; scanf("%d%d",&n,&m); for(int i=1;i<=m;i++) scanf("%d%d%d",&e[i].x,&e[i].y,&e[i].v); for(int i=1;i<=n;i++) fa[i]=i; sort(e+1,e+m+1,cmp); // for(int i=1;i<=m;i++) // printf("%d %d ",i,e[i].v); int k=0,tot=0; for(int i=1;i<=m;i++) { if(e[i].v!=e[i-1].v) { a[++k].l=i; a[k-1].r=i-1; } int fx=find(e[i].x),fy=find(e[i].y); if(fx!=fy) { fa[fx]=fy; a[k].cnt++; tot++; } } if(tot!=n-1) { printf("0"); return 0; } a[k].r=m; int ans=1; for(int i=1;i<=n;i++) fa[i]=i; for(int i=1;i<=k;i++) { // cout<<i<<‘ ‘<<a[i].cnt<<‘ ‘<<a[i].l<<‘ ‘<<a[i].r<<endl; if(!a[i].cnt)continue ; sum=0; // cout<<‘x‘<<i<<endl; dfs(a[i].l,0,i); ans=ans*sum%mod; // cout<<sum<<endl; for(int j=a[i].l;j<=a[i].r;j++) { int x=e[j].x,y=e[j].y; int fx=find(x),fy=find(y); if(fx!=fy)fa[fx]=fy; } } printf("%d",ans); return 0; }
以上是关于最小生成树计数的主要内容,如果未能解决你的问题,请参考以下文章