HDU 4408 Minimum Spanning Tree (minimum spanning tree count)

Question: Calculate the minimum number of spanning trees.

Problem solution: minimum spanning tree count
If we don't consider the minimum, we know that the spanning tree count can be calculated by the matrix tree theorem. We might as well consider the smallest first.

In Kruskal, each time we select the shortest edge to connect two connected blocks, for a set of edges of equal length, no matter which one we choose first, the final connectivity G0 is the same, that is, all edges of this set The connection after insertion is the same as G0, but the graph is different.

From this we can find that we can divide the edges of the same length into one stage, and this stage is completely independent of the subsequent stages. Then we multiply the number of plans at each stage, which is actually the answer.

Consider a single stage, and the connected blocks formed by the previous stage can be reduced, and the lengths of the edges are equal, and the spanning tree count must be guaranteed to be the minimum spanning tree.

And check set fa is the connection block to which the node belongs before the current stage, and the connection block it is in after the edge of the current stage of ka is connected. Two are used because when the same edge is connected, the connection of the previous stage is considered. We connect all the ones that meet the requirements, and then run the count.

#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
*/

Guess you like

Origin blog.csdn.net/qq_43680965/article/details/107848533