BZOJ1016 [JSOI2008]最小生成树计数 Kruskal

欢迎访问~原文出处——博客园-zhouzhendong

去博客园看该题解


题目传送门 - BZOJ1016


 

题意概括

  现在给出了一个简单无向加权图。你不满足于求出这个图的最小生成树,而希望知道这个图中有多少个不同的最小生成树。(如果两颗最小生成树中至少有一条边不同,则这两个最小生成树就是不同的)。

  答案对于31011取模。


 

题解

  先考虑错误的prim——

  这个是我的第一感,拿到题目,prim一发,发现错了。至于为什么,童鞋们可以根据下面的正解找一找反例。

 

  然而答案是kruskal。

  我们发现,一个图的最小生成树,如果有多种,那么无论如何,把他们各自的连边按照边权排序之后,序列在数值和数值个数上一定是相同的。

  也就是说,如果在方案A中,我们共取了x条权值为y的边,那么在方案B中,一定有且仅有x条权值为y的边。

  那么怎么证明?

  博主大蒟蒻实在太弱,难以给出好的证明。下面只是博主的一面之词。

  假设最小生成树的边集为E,对于其中的一个子集e,我们假设有边集e',|e| = |e'|,(我们假设e[i].z为边i的权值)且Σe[i].z = Σe'[i].z , 而且,e构成的连通分量中的节点和e'相同。

  那么,必然存在一种方案e'',使得e''在满足以上条件的基础上,改变取边方案,使得Σe''[i].z < Σe[i].z,那么之前的假设就失败了。

  太笼统了?

  是的,想知道明细的童鞋可以—— 百度一下,你就知道。

 

  然后,对于每一类边权相同的边,我们dfs一下有多少种方案,然后乘法原理即可。

  题目保证边权相同的边数不超过10。

  那么,为什么可以独立呢?

  那么我们就要保证dfs出来的方案起到的效果是一定的。

  (对于同一边权)

  假设方案A可以达到连接某些节点的效果,方案B可以达到连接另一些节点的效果。

  那么必然存在方案C,使得效果更佳。


代码

#include <cstring>
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <vector>
using namespace std;
typedef long long LL;
const int mod=31011,N=100+5,M=1000+5;
int n,m,tot,fa[N],cnt[M];
struct Edge{
    int x,y,z;
}e[M];
vector <int> id[M];
bool cmp(Edge a,Edge b){
    return a.z<b.z;
}
int getf(int k){
    return fa[k]==k?k:getf(fa[k]);
}
int dfs(int bh,int pos,int chosen){
    if (pos>=id[bh].size())
        return chosen==cnt[bh];
    int ans=dfs(bh,pos+1,chosen),ID=id[bh][pos];
    int x=getf(e[ID].x),y=getf(e[ID].y);
    if (x==y)
        return ans;
    fa[x]=y;
    ans=(ans+dfs(bh,pos+1,chosen+1))%mod;
    fa[x]=x;
    return ans;
}
int main(){
    scanf("%d%d",&n,&m);
    for (int i=1;i<=m;i++)
        scanf("%d%d%d",&e[i].x,&e[i].y,&e[i].z);
    sort(e+1,e+m+1,cmp);
    for (int i=1;i<=n;i++)
        fa[i]=i;
    memset(cnt,0,sizeof cnt);
    int tote=tot=0;
    for (int i=1;i<=m;i++){
        if (e[i].z!=e[i-1].z)
            tot++;
        id[tot].push_back(i);
        int x=getf(e[i].x),y=getf(e[i].y);
        if (x==y)
            continue;
        cnt[tot]++;
        fa[x]=y;
        tote++;
    }
    if (tote<n-1){
        puts("0");
        return 0;
    }
    for (int i=1;i<=n;i++)
        fa[i]=i;
    int ans=1;
    for (int i=1;i<=tot;i++){
        ans=1LL*ans*dfs(i,0,0)%mod;
        for (int j=0;j<id[i].size();j++){
            int ID=id[i][j];
            int x=getf(e[ID].x),y=getf(e[ID].y);
            if (x==y)
                continue;
            fa[x]=y;
        }
    }
    printf("%d",ans);
    return 0;
}

 

转载于:https://www.cnblogs.com/zhouzhendong/p/BZOJ1016.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值