生成树专题----Minimum Spanning Tree 最小生成树计数

XXX is very interested in algorithm. After learning the Prim algorithm and Kruskal algorithm of minimum spanning tree, XXX finds that there might be multiple solutions. Given an undirected weighted graph with n (1<=n<=100) vertexes and m (0<=m<=1000) edges, he wants to know the number of minimum spanning trees in the graph.
Input
There are no more than 15 cases. The input ends by 0 0 0.
For each case, the first line begins with three integers — the above mentioned n, m, and p. The meaning of p will be explained later. Each the following m lines contains three integers u, v, w (1<=w<=10), which describes that there is an edge weighted w between vertex u and vertex v( all vertex are numbered for 1 to n) . It is guaranteed that there are no multiple edges and no loops in the graph.
Output
For each test case, output a single integer in one line representing the number of different minimum spanning trees in the graph.
The answer may be quite large. You just need to calculate the remainder of the answer when divided by p (1<=p<=1000000000). p is above mentioned, appears in the first line of each test case.
Sample Input
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
Sample Output
4

参考博客
1、https://www.cnblogs.com/jcf94/p/4071098.html
2、https://blog.csdn.net/gatevin/article/details/48098075
生成树计数可以使用Matrix-Tree定理解决,本题最主要的区别是有了一个最小生成树的额外条件。

首先考虑一下如何得到最小生成树。

Kruskal算法的基本思想是,按照边长排序,然后不断将短边加入集合,最终一步如果能成功把n-1条边都加入同一个集合,则找到了最小生成树。在维护集合时,可以使用并查集来快速处理。

如果把Kruskal的过程按照边长划分成多个阶段,实际上是处理了所有短边的连通性之后继续处理下一个长度的边的连通性,并依次继续处理剩下边的连通性。然后我们可以发现,不同长度的边之间的连通性互不影响!!!

假设存在n1条长度为c1的边,n2条长度为c2的边…则Kruskal首先处理c1边的连通性,然后处理c2边的连通性,对于c1边的连通性的处理可能有多种方案,即从n1条边中取出一定数量的边构成最大连通图,但是最终处理完之后的结果对于c2来说是完全一样的。因此算法就出来了,在Kruskal的基础上,使用Matrix-Tree定理处理每个阶段生成树的种数,最后将所有阶段的结果相乘即可。

实现方法:通过Kruskal的边的阶段性将整个过程分为多次求缩点后形成连通分量的过程, 那么对于每个阶段就是几个生成树的方案的乘积, 然后将新产生的连通分量缩点, 一直进行到Kruskal结束。

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int inf = 0x3f3f3f3f;
#define mp make_pair
#define pb push_back
#define fi first
#define se second

typedef struct Node{
    int u,v,w;
    friend bool operator < (const Node &p,const Node &q){
        return p.w < q.w;
    }
}Node;
Node edge[1010];
vector<int>graph[105]; //存储每个阶段的连通图,以根节点存,graph[i] > 0,vector里的值
                       //都是一个连通图里的
int a[105][105];       //存储某个阶段内某个连通图内的行列式,求生成树数量
int g[105][105];       //存储每个阶段要处理以及之前的连通分量的代表节点的关系
bool vis[105];         //存储每个阶段要处理的连通分量的代表节点
LL mod;
int n,m;
int fa[105],ka[105];   //fa数组用在Kruskal;
                       //ka数组维护连通分量,因为每个阶段,当找到u,v不能进行合并操作(合并之后就只能找到一棵树),为了让g矩阵正确存储
                       //需要先对ka数组进行合并操作,临时储存当前阶段的合并,之后处理的时候更新fa即可。

//求解行列式模板(取模)
LL det(int n)
{
    LL res = 1;
    for(int i = 1;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] - t * a[j][k] % mod + mod) % mod;
                    swap(a[i][k],a[j][k]);
                }
                res = (-res + mod) % mod;
            }
        }
        res = res * a[i][i] % mod;
    }
    return (res + mod) % mod;
}

//并查集
int Find(int x,int *f)
{
    if(x == f[x])
        return x;
    return f[x] = Find(f[x],f);
}

//处理每个阶段
LL matrixTree()
{
    for(int i = 1;i <= n;++i){
        if(vis[i]){
            graph[Find(i,ka)].pb(i);
            vis[i] = false;
        }
    }
    LL res = 1;
    for(int i = 1;i <= n;++i){
        if(graph[i].size() > 1){
            memset(a,0,sizeof(a));
            int len = graph[i].size();
            for(int j = 0;j < len;++j){
                for(int k = j + 1;k < len;++k){
                    int u = graph[i][j],v = graph[i][k];
                    a[j + 1][k + 1] = -g[u][v];
                    a[k + 1][j + 1] = a[j + 1][k + 1];
                    a[j + 1][j + 1] += g[u][v];
                    a[k + 1][k + 1] += g[u][v];
                }
            }
            res = res * det(len - 1) % mod;
            for(int j = 0;j < len;++j){
                fa[graph[i][j]] = i;
            }
        }
    }
    for(int i = 1;i <= n;++i){
        fa[i] = Find(i,fa);
        ka[i] = fa[i];
        graph[i].clear();
    }
    return res;
}

int main()
{
    while(~scanf("%d %d %lld",&n,&m,&mod)){
        if(n == 0 && m == 0 && mod == 0)
            break;
        for(int i = 0;i < m;++i){
            scanf("%d %d %d",&edge[i].u,&edge[i].v,&edge[i].w);
        }
        sort(edge,edge + m);
        for(int i = 1;i <= n;++i)
            fa[i] = ka[i] = i;
        memset(vis,false,sizeof(vis));
        memset(g,0,sizeof(g));
        LL ans = 1;
        int pre = edge[0].w;
        for(int i = 0;i < m;++i){
            int u = Find(edge[i].u,fa),v = Find(edge[i].v,fa);
            if(u != v){
                ka[Find(u,ka)] = Find(v,ka);//只更新连通分量用的并查集
                vis[u] = vis[v] = true;
                g[u][v]++;
                g[v][u]++;
                //注意这里不更新Kruskal的并查集, 在这一阶段结束才更新, 这是为了使得邻接矩阵代表出连通分量之间的关系
            }
            if(i == m - 1 || edge[i + 1].w != pre){
                ans = ans * matrixTree() % mod;
                pre = edge[i + 1].w;
            }
        }
        //考虑图不连通的情况
        for(int i = 2;i <= n;++i){
            if(fa[i] != fa[i - 1]){
                ans = 0;
                break;
            }
        }
        printf("%lld\n",ans % mod);
    }
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值