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

Minimum Spanning Tree

Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 2672    Accepted Submission(s): 889

 

Problem Description

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

最小生成树计数问题,看了几天了没看懂,awsl,这里贴一个比较好的模板吧。

ε=(´ο`*)))唉  模板

思想就是:

在Kruskal的基础上,每完成一个阶段(检查完一个长度),就将所有遍历过的点缩成一个点,然后用Matrix-Tree定理计算该点与下一组点组成的连通图中生成树的个数。最终把每一个阶段的结果相乘即可。

/* 
 * Matrix-Tree定理(Kirchhoff矩阵-树定理) 
 * 图G的所有不同的生成树的个数等于其Kirchhoff矩阵C[G]任何一个n - 1阶主子式的行列式的绝对值 
 * 相关概念: 
 * 定义一个如G的Kirchhoff矩阵C[G]为G的度数矩阵D[G]与邻接矩阵A[G]的差 
 * 即C[G] = D[G] - A[G] 
 * G的度数矩阵D[G]: 是一个n*n的矩阵, 满足当i != j 时d[ij] = 0, 当i == j时d[ij] = (v[i]的度数) 
 * G的邻接矩阵A[G]: 是一个n*n的矩阵, 满足如果v[i]与v[j]之间有边相连则a[ij] = 1否则a[ij] = 0, 其实应该是v[i]和v[j]之间相连的边数
 * n - 1阶主子式: 即矩阵去掉第i行和i列的所有元素之后得到的矩阵(1 <= i <= n) 
 */
#include <bits/stdc++.h>
#define maxn 110
#define maxm 1010
using namespace std;
const double eps(1e-8);
typedef long long lint;

lint mod, n, m;
vector<int> gra[maxn];

//边
struct Edges
{
    int u, v, w;
    Edges(int _u, int _v, int _w) : u(_u), v(_v), w(_w){}
    Edges(){}
};

bool operator < (const Edges &e1, const Edges &e2)
{
    return e1.w < e2.w;
}

Edges edge[maxm];

//矩阵
struct Matrix
{
    lint a[maxn][maxn];
    Matrix()
    {
        for(int i = 0; i < maxn; i++)
            for(int j = 0; j < maxn; j++)
                a[i][j] = (i == j);
    }
    lint* operator[](int x)
    {
        return a[x];
    }
    //求Kirchhoff矩阵C[G]矩阵求值模板  --> 看不懂
    lint det(int n)
    {
        for(int i = 1; i < n; i++)
            for(int j = 1; j < n; j++)
                a[i][j] %= mod;
        lint ret = 1;
        for(int i = 1; i < n; i++)
        {
            for(int j = i + 1; j < n; j++)
                while(a[j][i])
                {
                    lint tmp = a[i][i] / a[j][i];
                    for(int k = i; k < n; k++) a[i][k] = (a[i][k] - a[j][k]*tmp) % mod;
                    for(int k = i; k < n; k++) swap(a[i][k], a[j][k]);
                    ret = -ret;
                }
            if(a[i][i] == 0) return 0;
            ret = ret*a[i][i] % mod;
        }
        return (ret + mod) % mod;
    }
};
//矩阵相减
Matrix operator - (const Matrix &m1, const Matrix &m2)
{
    Matrix ret;
    for(int i = 0; i < maxn; i++)
        for(int j = 0; j < maxn; j++)
            ret[i][j] = m1.a[i][j] - m2.a[i][j];
    return ret;
}

Matrix K, A;
    
bitset <maxn> vis;//记录Kruskal每一阶段的图新的要处理的连通分量的代表结点(缩点处理之后的)

int fa[maxn], ka[maxn];//两个并查集, 一个用在Kruskal, 一个维护连通分量
int find(int x, int *f)
{
    return x == f[x] ? x : f[x] = find(f[x], f);
}
    
lint matrixTree()
{
    for(int i = 1; i <= n; i++) if(vis[i])//找出连通分量
    {
        gra[find(i, ka)].push_back(i);//这里ka能反映出新图的连通关系, 而fa还没更新
        vis[i] = 0;
    }
    lint ret = 1;
    for(int i = 1; i <= n; i++) if(gra[i].size() > 1)//枚举计算各个连通分量造成的不同生成树数量
    {
        memset(K.a, 0, sizeof(K.a));
        int sz = gra[i].size();
        for(int x = 0; x < sz; x++)
        {
            for(int y = x + 1; y < sz; y++)//每个连通分量求Kirchhoff矩阵
            {
                int u = gra[i][x], v = gra[i][y];
                K[x + 1][y + 1] = 0 - A[u][v];
                K[y + 1][x + 1] = K[x + 1][y + 1];
                K[x + 1][x + 1] += A[u][v] - 0;
                K[y + 1][y + 1] += A[v][u] - 0;
                
            }
        }
        ret = ret*K.det(sz) % mod;
        for(int j = 0; j < sz; j++) fa[gra[i][j]] = i;//更新并查集
    }
    for(int i = 1; i <= n; i++)//连通图缩点, 将连通分量并查集的根节点变为一致
    {
        fa[i] = find(i, fa);
        ka[i] = fa[i];
        gra[i].clear();
    }
    return ret;
}
    
void solve()
{
    while(scanf("%d %d %d", &n, &m, &mod), n || m || mod)
    {
        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;
        vis.reset();
        memset(A.a, 0, sizeof(A.a));//邻接矩阵
        lint ans = 1;
        int pre = edge[0].w;
        for(int i = 0; i < m; i++)
        {
            int fu = find(edge[i].u, fa), fv = find(edge[i].v, fa);
            if(fu != fv)
            {
                ka[find(fu, ka)] = find(fv, ka);//只更新连通分量用的并查集
                vis[fu] = 1;
                vis[fv] = 1;
                A[fu][fv]++;
                A[fv][fu]++;
                //注意这里不更新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(ka[i] != ka[i - 1])//图不连通
            {
                ans = 0;
                break;
            }
        printf("%I64d\n", ans % mod);
    }
}

int main()
{
    solve();
    return 0;
}

 

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值