HDU 4408 Minimum Spanning Tree (图的最小生成树计数 Kruskal + Matrix_Tree定理)

题目大意:

就是给出一个图求最小生成树的个数


大致思路:

表示写这个题的时候坑了一段时间....

首先根据网上的众多题解, 通过Kruskal的边的阶段性将整个过程分为多次求缩点后形成连通分量的过程, 那么对于每个阶段就是几个生成树的方案的乘积, 然后将新产生的连通分量缩点, 一直进行到Kruskal结束

对于每一阶段就是一个子图上的生成树个数的问题

这个地方需要注意在计算Kirchhoff矩阵的时候, 用到的邻接矩阵中A[i][j]应该是点vi和vj之间的边数, 而不是一直是1或者0....

对于每个连通分量在各个阶段的生成树个数用Matrix_Tree定理求解, 而对于连通分量缩点的问题用并查集维护, 注意一下Kruskal和连通分量的并查集更新顺序就行了


代码如下:

Result  :  Accepted     Memory  :  1940 KB     Time  :  78 ms

/*
 * Author: Gatevin
 * Created Time:  2015/8/29 23:43:44
 * File Name: Iki_Hiyori.cpp
 */
#include<iostream>
#include<sstream>
#include<fstream>
#include<vector>
#include<list>
#include<deque>
#include<queue>
#include<stack>
#include<map>
#include<set>
#include<bitset>
#include<algorithm>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cctype>
#include<cmath>
#include<ctime>
#include<iomanip>
using namespace std;
const double eps(1e-8);
typedef long long lint;

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

#define maxn 110
#define maxm 1010
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];
    }
    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、付费专栏及课程。

余额充值