bzoj1016[JSOI2008]最小生成树计数

49 篇文章 0 订阅
30 篇文章 0 订阅

http://www.elijahqi.win/archives/1111
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
这篇文章讲的real清楚 http://blog.csdn.net/sdfzyhx/article/details/52075151 下面是引用的原文
首先需要一个结论,对于一个图的不同最小生成树,每种方案所包含的每种权值的边的数量一定一致。换句话说,把每种方案包含的所有边的边权都写下来,写出来的序列一定都一样。关于这个结论的说明放在最后。
这样的话,可以先做一遍kruskal,记下每种边权的使用次数,然后对于每种边权进行dfs,判断有多少种合法的组合方式【一种方案合法意味着:1.加入每条边时,边的两端点一定属于不同的并查集,也就是仍然要符合kruskal的要求。2.加入的总边数等于开始统计的使用次数。第一条要求也就决定了不能用组合数进行计算,只能dfs,而因为相同边权的边不超过10,再加上一些最简单的剪枝,运行速度很快。】,然后用乘法原理即可。
注意:
1.dfs的时候要回溯,所以这里的并查集不能进行路径压缩。
2.处理完每种边之后要把这些边加上去,这才是kruskal的过程。
3.考虑图不连通,即不存在最小生成树的情况。
最后说一下原理。考虑kruskal的过程,只有当这一权值的边全部考虑之后才会考虑权值比他大的边。举个最简单的例子,假设有两种方案,第一种方案有边x1,x4,第二种有边x2,x3,且x1< x2< x3< x4。因为一条边只能连接两个连通块,那么x2,x3中一定有一个能起到x4的作用,那么这个能起作用的点和x1组成的方案才是最小生成树。
既然这样,不同的方案从何而来呢?来自于相同的权值的边,在排序后的顺序不同,而他们又起着相同的作用(也就是连接相同的联通块)【因为如果作用不一样的话,他们应该都被纳入方案】,那么先考虑这个和先考虑那个就会得出不同的方案。
于是,我们对每一组权值相同的边,知道了他们总的使用次数以后,进行暴力的枚举统计方案数。因为我们知道,不管怎么选,最后的结果,也就是给后面带来的影响,都是相同的【因为如果影响不同,那么就不应该现在从中挑选,当初kruskal的时候应该都选进来。】
那么这种做法,和直接暴力枚举的区别就显现了。暴力枚举是整个的指数级,而这种做法是把总数分成很多小块,在每一块里暴力枚举,最后的复杂度是每一块的指数相加,自然小了很多。

相当于我先用kruskal做出我的最小生成树 然后记录下一共使用了多少种权值
因为我是排好序的所以我给每个权值分段 并且记录下我每种权值都用了多少
然后根据上面的证明 我权值连接的应该都是相同的联通块才行
每次做完一种权值后就给他们连起来 (ps:造成的效果是一样的)所以随便连就可以了


#include<cstdio>
#include<algorithm>
#define mod 31011
#define N 110
#define M 1100
using namespace std;
inline int read(){
    int x=0;char ch=getchar();
    while (ch<'0'||ch>'9') ch=getchar();
    while (ch<='9'&&ch>='0'){x=x*10+ch-'0';ch=getchar();}
    return x;
}
struct node{
    int x,y,z;
}data[M];
struct node1{
    int num,l,r;
}group[M];
inline bool cmp(node a,node b){return a.z<b.z;}
int ans1,n,m,fa[N];
inline int find(int x){return fa[x]==x?x:find(fa[x]);}
void dfs(int id,int step,int now){
    if (step>group[id].r){if (now==group[id].num) ans1++;return;}
    dfs(id,step+1,now);
    int xx=find(data[step].x),yy=find(data[step].y);
    if (xx!=yy){
        fa[xx]=yy;dfs(id,step+1,now+1);
        fa[xx]=xx;fa[yy]=yy;
    }
}
int main(){
    freopen("bzoj1016.in","r",stdin);
    n=read();m=read();
    for (int i=1;i<=m;++i){
        int x=read(),y=read(),z=read();
        data[i].x=x;data[i].y=y;data[i].z=z;
    }
    sort(data+1,data+m+1,cmp);int g=0,cnt=0;
    for (int i=1;i<=n;++i) fa[i]=i;
    for (int i=1;i<=m;++i){
        if (data[i].z!=data[i-1].z) group[g].r=i-1,group[++g].l=i;
        int xx=find(data[i].x),yy=find(data[i].y);
        if (xx!=yy){
            ++cnt;group[g].num++;fa[xx]=yy;
        }
    }group[g].r=m;
    if (cnt<n-1) {printf("0\n");return 0;}
    int ans=1;
    for (int i=1;i<=n;++i) fa[i]=i;
    for (int i=1;i<=g;++i){
        ans1=0;dfs(i,group[i].l,0);
        (ans*=ans1)%=mod;
        for (int j=group[i].l;j<=group[i].r;++j){
            int xx=find(data[j].x),yy=find(data[j].y);
            if (xx!=yy) fa[xx]=yy;
        }
    }
    printf("%d\n",ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值