【BZOJ-1016】最小生成树计数

题目链接

题目描述

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

题解

首先有如下定理:
一个无向图所有的最小生成树中某种权值的边的数目均相同。
(太弱不会证)
给出一种理解:
我们在克鲁斯卡尔求最小生成树时的步骤是:
1.把边权排序
2.从小到大加边

那么可以看到我们在排序边权时我们并不关心同种边权的的边的顺序,也就是说同种边权无论什么顺序连出来的都是最小生成树,那么就一定这种边权的边的数目相同,不然肯定不是最小的。(是不是很有道理)

那么就开始考虑咋办。
显然要把边权从小到大,同种边权一起考虑。
既然我们无论什么顺序加同种边权的边连出来的都是最小生成树。
我们考虑一种边权时,每有一个新的加边顺序,肯定生成了形态不同的森林(但联通块个数相同)
所以我们就 O ( 2 10 ) 大暴力把这种不同的加边顺序个数搞出来,然后乘法原理即可。
注意一种边权选的个数与最小生成树中选的个数相同。

代码如下:

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#include<set>
#define Set(a,b) memset(a,b,sizeof(a))
#define Copy(a,b) memcpy(a,b,sizeof(a))
using namespace std;
inline int read()
{
    int x=0;char ch=getchar();int t=1;
    for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') t=-1;
    for(;ch>='0'&&ch<='9';ch=getchar()) x=(x<<1)+(x<<3)+(ch-48);
    return x*t;
}
typedef long long ll;
const int N=210;
const int M=2e3+10;
const int mod=31011;
int fa[N];
int tmp[N];
struct edge{
    int from;int to;ll w;int id;bool use;
    inline bool operator <(edge b)const{
        return w<b.w;
    }
}a[M];
int n,m;
int cn=0;
int num[M];
inline void add(int x,int y,ll w){a[++cn]=(edge){x,y,w};}

inline int find(int x){return fa[x]==x? x:fa[x]=find(fa[x]);}
#define lowbit(a) ((a)&(-a))
inline int count(int S)
{
    register int tot=0;
    while(S) tot++,S-=lowbit(S);
    return tot;
}
inline ll MST()
{
    sort(a+1,a+1+m);register ll res=0;register int tot=0;
    int h=0;
    a[0].w=-123;
    for(register int i=1;i<=m;i++){
        if(a[i].w!=a[i-1].w) h++;
        a[i].id=h;
    }
    for(register int i=1;i<=m;i++){
        if(tot==n-1) break;
        register int u=a[i].from,v=a[i].to;
        register int fx=find(u),fy=find(v);
        if(fx==fy) continue;
        tot++;fa[fx]=fy;
        res+=a[i].w;num[a[i].id]++;
    }
    if(tot!=n-1) return -1;
    return res;
}
int main()
{
    n=read();m=read();register int x,y,w;
    register int ans1=n-1;
    register ll ans2=0,ans3=1;
    for(register int i=1;i<=n;i++) fa[i]=i;
    for(register int i=1;i<=m;i++) {
        x=read();y=read();w=read();add(x,y,1ll*w);
    }
    ans2=MST();
    if(ans2==-1){puts("0");return 0;}
    for(register int i=1;i<=n;i++) fa[i]=tmp[i]=i;
    for(register int i=1;i<=m;) {//顺次考虑每种权值的边
        register int j;for(j=i;a[j].w==a[j+1].w;j++);//搞出权值相同的边

        register int tot=j-i+1;

        if(num[a[j].id]){//最小生成树里有这条边
            register int use=num[a[j].id];register int now=0;
            for(register int S=1;S<(1<<tot);S++){//枚举这种权值的边的选择情况
                if(count(S)!=use) continue;//定理,不然不是最小生成树
                bool can=1;
                Copy(fa,tmp);
                for(register int k=i;k<=j;k++){
                    if((S&(1<<(k-i)))==0) continue;
                    register int fx=find(a[k].from);register int fy=find(a[k].to);
                    if(fx==fy) {can=0;break;}
                    fa[fx]=fy;
                }
                if(can) now++;
            }
            ans3=(ans3*now)%mod;
            Copy(fa,tmp);
            for(register int k=i;k<=j;k++){//搞完一种权值后要更新当前森林,即做一遍普通的最小生成树
                register int fx=find(a[k].from);register int fy=find(a[k].to);
                if(fx==fy) continue;
                fa[fx]=fy;
            }
            Copy(tmp,fa);//更新森林
        }
        i=j+1;
    }
    printf("%lld\n",ans3);
}

猜你喜欢

转载自blog.csdn.net/element_hero/article/details/79390135