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

本文详细介绍了如何计算最小生成树的数量。首先对边进行排序,然后逐步连接未联通的点,通过维护联通块的祖先信息来避免重复计数。在使用Matrix-Tree定理时,考虑了联通块缩点后的新图邻接矩阵应表示边的数量。通过对每个新联通块计数并累乘,最终得到最小生成树的总数。在整个过程中,还需要不断更新联通块信息以确保正确性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

http://acm.hdu.edu.cn/showproblem.php?pid=4408

将E中的所有边按照权值由小到大进行排序,然后按照从小到大的顺序去扫描每一条边,将未联通的点联通,权值累加,最后得到的图G’就是图G的最小生成树。将所有权值相同的边看成一个阶段整体处理,这一阶段生成树个数和下一阶段是独立的。
我们将求最小生成树所用的祖先存入father数组,将联通块的祖先存进U数组(相当于联通块缩成点)。用vis[i]=1标记联通块的祖先。用link[i][j]表示两个原本独立的联通块(祖先分别是i和j)的联通分量的度(连通的边数)。
在用Matrix-Tree时,邻接矩阵A[i][j]是vi和vj之间的边数,而不是1或0.
对于加入相同权值same后的新图可能会形成多个联通块,这时需要对每个联通块计数。找到每个联通块的祖先,将属于这个祖先的点计算。
• 确定祖先和该联通块的所有点
首先寻找的联通块应该含有新加进来的边,否则如果该联通块和未加边的联通块(上一阶段)完全一样,那就重复计数了。所以在加边时用vis数组记录father数组中的祖先是否被访问,如果被访问那么将属于这个联通块的所有点看作一个点,添加进新图的联通块,将新图的联通块的每个点都存入祖先的数组vec中。
• 确定联通块内的点与点是否直接联通和每个点的度数
枚举每个新图的联通块的祖先,在Matrix-Tree定理中,图G的邻接矩阵A[G]被定义为:当vi和vj直接相连时A[i][j]=1,否则A[i][j]=0。但是由于这里我们将旧图的联通块缩点了,那么新图的点与点应看成是旧图的联通块与联通块,所以新图的邻接矩阵应是点与点的边数,而不是非1即0得关系(而且link[i][j]=link[j][i])。
我们对新图的每个联通块求生成树的个数,然后累乘,将所有联通块求完后需要清空vec数组。同时还要把U数组和father数组更新,因为这时所有的新联通块和旧图的联通块都要合并成一个新的联通块。
最后还要检查图是否联通了(所有点的祖先都相同)。

#include <iostream>
#include <algorithm>
#include <cstdlib>
#include <cstring>
#include <vector>
#include <string>
#include <cmath>
#include <set>
using namespace std;
const int maxn = 100 + 5;
const int maxm = 1000 + 5;
typedef long long ll;
ll p, a[maxn][maxn];
int vis[maxn], fa[maxn], U[maxm], link[maxn][maxn];
vector<int> vec[maxn];
struct Edge {
    int u, v, w;
    Edge(int u = 0, int v = 0, int w = 0) : u(u), v(v), w(w) {}
    bool operator <(const Edge& rhs) const {
        return w < rhs.w;
    }
} edge[maxm];

void init(int n) {
    memset(link, 0, sizeof(link));
    memset(vis, 0, sizeof(vis));
    for(int i = 1; i <= n; ++i) fa[i] = i;
}

int find(int x, int *arr) {
    return arr[x] == x ? x : arr[x] = find(arr[x], arr);
}

ll det(int n) {
    for(int i = 0; i < n; ++i) {
        for(int j = 0; j < n; ++j) {
            a[i][j] = (a[i][j] % p + p) % p;
        }
    }
    ll ret = 1;
    int flg = 0;
    for(int i = 0; i < n; ++i) {
        for(int j = i + 1; j < n; ++j) {
            while(a[j][i]) {
                ll t = a[i][i] / a[j][i];
                for(int k = i; k <= n; ++k)
                    a[i][k] = (a[i][k] - a[j][k] * t) % p;
                for(int k = i; k <= n; ++k)
                    swap(a[i][k], a[j][k]);
                ++flg;
            }
        }
        if(a[i][i] == 0) return 0;
        ret = ret * a[i][i] % p;
    }
    if(flg & 1) ret = -ret;
    return (ret + p) % p;
}

ll solve(int n, int m) {
    sort(edge, edge + m);
    int same = -1;
    ll ret = 1;
    for(int i = 0; i <= m; ++i) {
        if(edge[i].w != same || i == m) {
            for(int j = 1; j <= n; ++j) {
                if(vis[j]) {
                    int fj = find(j, U);
                    vec[fj].push_back(j);
                    fa[j] = fj;
                    vis[j] = 0;
                }
            }
            for(int j = 1; j <= n; ++j) {
                int sz = vec[j].size();
                if(sz <= 1) continue;
                memset(a, 0, sizeof(a));
                for(int k = 0; k < sz; ++k) {
                    for(int h = k + 1; h < sz; ++h) {
                        int u = vec[j][k];
                        int v = vec[j][h];
                        a[k][h] -= link[u][v];
                        a[h][k] = a[k][h];
                        a[k][k] += link[u][v];
                        a[h][h] += link[v][u];
                    }
                }
                ret = ret * det(sz - 1) % p;
            }
            for(int j = 1; j <= n; ++j) {
                U[j] = fa[j] = find(j, fa);
                vec[j].clear();
            }
            if(i == m) break;
            same = edge[i].w;
        }
        int u = edge[i].u, v = edge[i].v;
        int fu = find(u, fa), fv = find(v, fa);
        if(fu == fv) continue;
        vis[fu] = vis[fv] = 1;
        U[find(fv, U)] = find(fu, U);
        link[fu][fv]++, link[fv][fu]++;
    }
    int flg = 1, com = find(1, fa);
    for(int i = 2; i <= n; ++i) {
        if(com != find(i, fa)) {
            flg = 0;
            break;
        }
    }
    if(!flg) ret = 0;
    return (ret + p) % p;
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);

    int n, m;
    while(cin >> n >> m >> p) {
        if(n == 0 && m == 0 && p == 0) break;
        init(n);
        for(int i = 0; i < m; ++i) {
            int u, v, w;
            cin >> u >> v >> w;
            edge[i] = Edge(u, v, w);
        }
        cout << solve(n, m) << endl;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值