HDU 4408 Minimum Spanning Tree (最小生成树计数)

题意:计算最小生成树个数。

题解:最小生成树计数
若不考虑最小,生成树计数我们知道,可以用矩阵树定理求。我们不妨先考虑最小。

Kruskal中,我们每次选取最短的边将两个连通块连上,对于长度相等的边的集合,无论我们先选哪一条,最后构成的连通情况G0是一样的,即把这个集合的边全部插入后的连通情况与G0相同,但是图不一样。

由此我们可以发现,我们可以将相同长度的边划分为一个阶段,而该阶段是完全与后面的阶段独立的。那么我们将每一个阶段的方案数相乘,其实就是答案。

单独考虑一个阶段,而之前的阶段构成的连通块可以缩点,边长度都相等,跑生成树计数一定能保证是最小生成树。

并查集fa是当前阶段之前,节点所属的联通块,ka当前阶段的边连接后它在的联通块。用两个是因为相同边连接时考虑的是上个阶段的连通情况,我们把符合要求的都连上,再跑计数。

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<cstdio>
#include<string>
#include<cstring>
#include<algorithm>
#include<queue>
#include<stack>
#include<cmath>
#include<vector>
#include<fstream>
#include<set>
#include<map>
#include<sstream>
#include<iomanip>
#define ll long long
using namespace std;
int n, m, MOD, u, v, w, g[111][111], fa[111], ka[111], vis[111];
struct node {
    
    
    int u, v, w;
    bool operator<(const node& x)const {
    
    
        return w < x.w;
    }
}edge[1111];
int Find(int x, int Parent[]) {
    
    
    return Parent[x] == x ? x : Parent[x] = Find(Parent[x], Parent);
}
vector<int> gra[111];
struct Matrix {
    
    
    int mat[111][111];
    ll ans = 1;
    void init() {
    
    
        memset(mat, 0, sizeof(mat));
    }
    int det(int n) {
    
    
        ll res = 1;
        for (int i = 1; i <= n; i++) {
    
    
            if (!mat[i][i]) {
    
     //若果对角线元素为0,把此行都一都移到下一行去
                bool flag = false;
                for (int j = i + 1; j <= n; j++) {
    
     //从i+1行开始找i列中的第一个不为0的元素,与现在的行交换
                    if (mat[j][i]) {
    
    //找到了该列不为0的元素,
                        flag = 1; //标记,交换
                        for (int k = i; k <= n; k++) swap(mat[i][k], mat[j][k]);
                        res = -res;// 换行系数变为负数
                        break; //退出.
                    }
                }
                if (!flag) return 0; //这一行全部为0,行列式值为0
            }
            for (int j = i + 1; j <= n; j++) {
    
    
                while (mat[j][i]) {
    
     //从下面的行找一个不为0的元素与第i行进行消元
                    ll t = mat[i][i] / mat[j][i];
                    for (int k = i; k <= n; k++) {
    
    
                        mat[i][k] = (mat[i][k] - t * mat[j][k]) % MOD;
                        swap(mat[i][k], mat[j][k]);//消元后,把0的行换到下面来。
                    }
                    res = -res;
                }
            }
            res *= mat[i][i];//对角线元素相乘
            res %= MOD;
        }
        return (res + MOD) % MOD;
    }
    void matrix_tree() {
    
    
        for (int i = 0; i < n; i++) gra[i].clear();
        for (int i = 0; i < n; i++) {
    
    
            if (!vis[i]) continue;  //考虑当前阶段的点
            gra[Find(i, ka)].push_back(i);  //有多个连通块,我们放进vector
            vis[i] = 0;
        }
        for (int i = 0; i < n; i++) {
    
    
            init();
            for (int j = 0; j < gra[i].size(); j++) {
    
    
                for (int k = j + 1; k < gra[i].size(); k++) {
    
    
                    int u = gra[i][j];
                    int v = gra[i][k];
                    mat[j][k] -= g[u][v];     //有重边
                    mat[k][j] -= g[u][v];
                    mat[j][j] += g[u][v];
                    mat[k][k] += g[u][v];
                }
            }
            ans = (ans * det(gra[i].size() - 1)) % MOD;
            //更新连通块中每个点的fa,保存当前阶段连通情况(只考虑点,不考虑图是怎样的 )
            for (int j = 0; j < gra[i].size(); j++) fa[gra[i][j]] = i;
        }
        for (int i = 0; i < n; i++) ka[i] = Find(i, fa);
    }
}ma;
int main() {
    
    
	while (~scanf("%d%d%d", &n, &m, &MOD) && n) {
    
    
        for (int i = 0; i < m; i++) {
    
    
            scanf("%d%d%d", &u, &v, &w);
            edge[i] = {
    
     u - 1, v - 1, w };
        }
        sort(edge, edge + m);
        memset(g, 0, sizeof(g));
        for (int i = 0; i < n; i++) ka[i] = fa[i] = i;
        ma.ans = 1;
        for (int i = 0; i <= m; i++) {
    
    
            if (i && edge[i].w != edge[i - 1].w || i == m) {
    
    
                ma.matrix_tree();  //相同长度的边都连上了
            }
            int pa = Find(edge[i].u, fa);  //连接缩点之后的两个点
            int pb = Find(edge[i].v, fa);
            if (pa == pb) continue;   //kruskal只考虑上个阶段两个不同连通块
            vis[pa] = vis[pb] = 1;    //标记是属于当前阶段的点
            ka[Find(pa, ka)] = Find(pb, ka);  //连接两个连通块
            g[pa][pb]++;
            g[pb][pa]++;
        }
        int flag = 1;
        for (int i = 1; i < n; i++) if (fa[i] != fa[i - 1]) flag = 0;
        printf("%lld\n", flag ? ma.ans % MOD : 0);
	}
	return 0;
}

/*
5 10 12
2 5 3
2 4 2
3 1 3
3 4 2
1 2 3
5 4 3
5 1 3
4 1 1
5 3 3
3 2 3
0 0 0
*/

猜你喜欢

转载自blog.csdn.net/qq_43680965/article/details/107848533