【概述】
给出一个由 n 个点和 m 条边构成的简单无向加权图,有时需要对生成树计数或对最小生成树计数。
当对生成树计数时,利用基尔霍夫矩阵的 Matrix-Tree 定理即可解决,而对最小生成树计数时,根据数据范围的不同,所采用的方法也不同。
关于基尔霍夫矩阵:点击这里
【生成树计数】
对于生成树的计数,一般采用矩阵树定理(Matrix-Tree 定理)来解决。
Matrix-Tree 定理的内容为:对于已经得出的基尔霍夫矩阵,去掉其随意一行一列得出的矩阵的行列式,其绝对值为生成树的个数
因此,对于给定的图 G,若要求其生成树个数,可以先求其基尔霍夫矩阵,然后随意取其任意一个 n-1 阶行列式,然后求出行列式的值,其绝对值就是这个图中生成树的个数。
LL K[N][N];
LL gauss(int n){//求矩阵K的n-1阶顺序主子式
LL res=1;
for(int i=1;i<=n-1;i++){//枚举主对角线上第i个元素
for(int j=i+1;j<=n-1;j++){//枚举剩下的行
while(K[j][i]){//辗转相除
int t=K[i][i]/K[j][i];
for(int k=i;k<=n-1;k++)//转为倒三角
K[i][k]=(K[i][k]-t*K[j][k]+MOD)%MOD;
swap(K[i],K[j]);//交换i、j两行
res=-res;//取负
}
}
res=(res*K[i][i])%MOD;
}
return (res+MOD)%MOD;
}
int main(){
int n,m;
scanf("%d%d",&n,&m);
memset(K,0,sizeof(K));
for(int i=1;i<=m;i++){
int x,y;
scanf("%d%d",&x,&y);
K[x][x]++;
K[y][y]++;
K[x][y]--;
K[y][x]--;
}
printf("%lld\n",gauss(n));
return 0;
}
【最小生成树计数】
根据给出的 n、m 的数据范围,最小生成树的计数分为简化版、加强版。
最小生成树的计数的核心为 MST 的以下两条性质:
- 每种权值的边的数量是固定的
- 不同的生成树中,某一种权值的边任意加入需要的数量后,形成的联通块状态是相同的
1.简化版
当每种权值的边不超过 10 条时,利用 dfs,暴力枚举即可解决,时间复杂度为
根据最小生成树的性质,先统计出每种权值需要的边的条数 cnt
然后进行 dfs,枚举第 i 种权值的边选择哪 cnt 条,然后根据乘法原理来统计答案
需要注意的是,为了能够快速分开连通块,并查集中不能使用路径压缩
struct Edge {
int x,y;
int dis;
bool operator < (const Edge &rhs)const{
return dis<rhs.dis;
}
} edge[N];
struct build {
int l,r;
int cnt;
} a[N];
int tot;
int n,m;
int father[N];
int sum;
int Find(int x) {
if(father[x]!=x)
return Find(father[x]);
return father[x];
}
void dfs(int x,int now,int num) {
if(now==a[x].r+1) {
if(num==a[x].cnt)//选指定的条数
sum++;
return ;
}
int fx=Find(edge[now].x);
int fy=Find(edge[now].y);
if(fx!=fy) {//选
father[fx]=fy;
dfs(x,now+1,num+1);
father[fx]=fx;
father[fy]=fy;
}
dfs(x,now+1,num);//不选
}
int Kruskal() {
sort(edge+1,edge+m+1);
for(int i=1; i<=n; i++)
father[i]=i;
int cnt=0;
for(int i=1; i<=m; i++) {
if(edge[i].dis!=edge[i-1].dis) {
tot++;
a[tot].l=i;
a[tot-1].r=i-1;
}
int x=Find(edge[i].x);
int y=Find(edge[i].y);
if(x!=y) {
father[y]=x;
a[tot].cnt++;
cnt++;
}
}
a[tot].r=m;
return cnt;
}
int main() {
scanf("%d%d",&n,&m);
int x,y,z;
for(int i=1; i<=m; i++)
scanf("%d%d%d",&edge[i].x,&edge[i].y,&edge[i].dis);
int num=Kruskal();
if(num!=n-1) {
printf("0");
return 0;
}
else{
for(int i=1; i<=n; i++)
father[i]=i;
int res=1;
for(int i=1; i<=tot; i++) {
sum=0;
dfs(i,a[i].l,0);
res=res*sum;
for(int j=a[i].l; j<=a[i].r; j++) {
int x=Find(edge[j].x);
int y=Find(edge[j].y);
if(x!=y)
father[y]=x;
}
}
printf("%d",res);
}
return 0;
}
2.加强版
当每种权值的边不超过 100 条,需要利用 Matrix-Tree 定理,时间复杂度为
根据 MST 的性质,枚举树边的权值 i,将权值不是 i 的树边都加入到图中然后进行缩点。将权值是 i 的原图中的边,在缩点后构造基尔霍夫矩阵,再利用 Matrix-Tree 定理求出方案数。
最终答案根据乘法原理进行计算即可。
struct Edge {
int x,y;
int dis;
bool operator < (const Edge &rhs) const {
return dis<rhs.dis;
}
} edge[N],tr[N];
int n,m;
int father[N];
int G[N][N];
int tot,bel[N],val[N];
int Find(int x) {
if(father[x]!=x)
return father[x]=Find(father[x]);
return x;
}
int Gauss(int n) {
int res=1;
for(int i=1; i<=n; i++) {
for(int k=i+1; k<=n; k++) {
while(G[k][i]) {
int d=G[i][i]/G[k][i];
for(int j=i; j<=n; j++)
G[i][j]=(G[i][j]-1LL*d*G[k][j]%MOD+MOD)%MOD;
swap(G[i],G[k]);
res=-res;
}
}
res=1LL*res*G[i][i]%MOD,res=(res+MOD)%MOD;
}
return res;
}
int Kruskal() {
sort(edge+1,edge+m+1);
for(int i=1; i<=n; i++)
father[i]=i;
int cnt=0;
for(int i=1; i<=m; i++) {
int fu=Find(edge[i].x);
int fv=Find(edge[i].y);
if(fu==fv)
continue;
father[fu]=fv,tr[++cnt]=edge[i];
if(edge[i].dis!=val[tot])
val[++tot]=edge[i].dis;
}
return cnt;
}
void addTreeEdge(int v) {
for(int i=1; i<n&&tr[i].dis!=v; i++){
int x=tr[i].x;
int y=tr[i].y;
father[Find(x)]=Find(y);
}
for(int i=n-1; i&&tr[i].dis!=v; i--){
int x=tr[i].x;
int y=tr[i].y;
father[Find(x)]=Find(y);
}
}
void rebuild(int v) {
memset(G,0,sizeof(G));
for(int i=1; i<=m; i++){
if(edge[i].dis==v){
int x=bel[edge[i].x];
int y=bel[edge[i].y];
G[x][y]--;
G[y][x]--;
G[x][x]++;
G[y][y]++;
}
}
}
int main() {
scanf("%d%d",&n,&m);
for(int i=1; i<=m; i++)
scanf("%d%d%d",&edge[i].x,&edge[i].y,&edge[i].dis);
int cnt=Kruskal();
if(cnt!=n-1) {
printf("0\n");
}
else{
int res=1;
for(int i=1; i<=tot; i++) {
for(int i=1; i<=n; i++)
father[i]=i;
addTreeEdge(val[i]);
int blo=0;
for(int i=1; i<=n; i++)
if(Find(i)==i)
bel[i]=++blo;
for(int i=1; i<=n; i++)
bel[i]=bel[Find(i)];
rebuild(val[i]);
res=1LL*res*Gauss(blo-1)%MOD;
}
printf("%d\n",res);
}
return 0;
}