【BZOJ1016】【JSOI2008】最小生成树计数 & 【BZOJ1543】生成树计数 (kruskal+matrix_tree定理)

Description

  现在给出了一个简单无向加权图。你不满足于求出这个图的最小生成树,而希望知道这个图中有多少个不同的
最小生成树。(如果两颗最小生成树中至少有一条边不同,则这两个最小生成树就是不同的)。由于不同的最小生
成树可能很多,所以你只需要输出方案数对31011的模就可以了。

Input

  第一行包含两个数,n和m,其中1<=n<=100; 1<=m<=1000; 表示该无向图的节点数和边数。每个节点用1~n的整
数编号。接下来的m行,每行包含两个整数:a, b, c,表示节点a, b之间的边的权值为c,其中1<=c<=1,000,000,0
00。数据保证不会出现自回边和重边。注意:具有相同权值的边不会超过10条。

Output

  输出不同的最小生成树有多少个。你只需要输出数量对31011的模就可以了。

Sample Input

4 6

1 2 1

1 3 1

1 4 1

2 3 2

2 4 1

3 4 1

Sample Output

8

题解:
先按照任意顺序对等长的边进行排序;
然后利用并查集将所有长度为L的边的处理当作一个整体看待;
可以定义一个vector来保存每一个连通块的边的信息;
即将原图划分成多个连通块,每个连通块里面的边的权值都相同;
针对每一个连通块构建对应的Kirchhoff矩阵C,利用Matrix_Tree定理求每一个连通块的生成树个数; 最后把他们的值相乘即可;
*Matrix_Tree定理:
G的所有不同的生成树的个数等于其Kirchhoff矩阵C[G]任何一个n-1阶主子式的行列式的绝对值;n-1阶主子式就是对于r(1≤r≤n),将C[G]的第r行,第r列同时去掉后得到的新矩阵,用Cr[G]表示;

另一种做法:由于权值小于等于10,直接2^10枚举权值,暴力看是否有MST,复杂度差不多能过。

代码如下:

#include<iostream>
#include<stdio.h>
#include<algorithm>
#include<string.h>
#include<math.h>
#define ll long long
#define inf 0x7f7f7f7f
#define mod 1000003
using namespace std;
struct edge
{
    int u,v,w,x;
    edge(){}
    edge(int a,int b,int c,int d):u(a),v(b),w(c),x(d){}
    inline bool operator< (const edge &rhs) const{return x < rhs.x;}
}e[100005];
struct count
{
    int l,r,cnt;
}g[100005];
int n,m,fa[50005],siz[50005];
int find(int x){return fa[x]==x?x:find(fa[x]);} 
void unio(int u,int v)
{
    if(siz[u]>siz[v]) fa[v]=u,siz[u]+=siz[v];
    else fa[u]=v,siz[v]+=siz[u];
}
bool kruskal()
{
    int cnt=0,u,v;
    for(int i=1;i<=m;i++)
    {
        u=find(e[i].u),v=find(e[i].v);
        if(u!=v)
        {
            unio(u,v);
            ++g[e[i].w].cnt;
            if(++cnt==n-1) return 1;
        }
    }
    return 0;
}
int dfs(int w,int i,int k)
{
    if(k==g[w].cnt) return 1;
    if(i>g[w].r) return 0;
    int ans=0,u=find(e[i].u),v=find(e[i].v);
    if(u!=v)
    {
        unio(u,v);
        ans=dfs(w,i+1,k+1);
        fa[u]=u,fa[v]=v;
    }
    return ans+dfs(w,i+1,k);
}
int main()
{
    int u,v,w,ans;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) fa[i]=i,siz[i]=1;
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d%d",&u,&v,&w);
        e[i]=edge(u,v,0,w);
    }
    sort(e+1,e+m+1);
    w=0;
    for(int i=1;i<=m;i++)
    {
        if(e[i].x==e[i-1].x) e[i].w=w;
        else
        {
            g[w].r=i-1;
            e[i].w=++w;
            g[w].l=i;
        }
    }
    g[w].r=m;
    ans=kruskal();
    for(int i=1;i<=n;i++) fa[i]=i,siz[i]=1;
    for(int i=1;i<=w;i++)
    {
        ans=ans*dfs(i,g[i].l,0)%mod;
        for(int j=g[i].l;j<=g[i].r;j++)
        {
            u=find(e[j].u),v=find(e[j].v);
            if(u!=v) unio(u,v);
        }
    }
    printf("%d\n",ans);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值