【BZOJ】1016: [JSOI2008]最小生成树计数

【题意】给定无向带权图,求最小生成树数。n<=100,m<=1000,相同权值边数<=10。

【算法】最小生成树(MST),DFS

【题解】首先需要一个结论:同个图的不同MST一定满足同个权值的边数相同

考虑kruskal算法的过程,已经统计了<x的所有边,现在考虑=x的边,根据生成树的性质一定要尽量选择能连通两个集合的边,所以同个权值的边一定是选择边数相同且必须对集合连通的影响相同。

先做一次MST得到每个权值的边数,然后dfs每个权值的选边情况得到总方案数。

dfs中每次只能选择当前能连通两个集合的边,回溯的话就在find的时候不进行路径压缩即可。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=1010,MOD=31011;
struct edge{int u,v,w;}e[maxn];
int be[maxn],ed[maxn],fa[maxn],num[maxn],n,m,tot;
bool cmp(edge a,edge b){return a.w<b.w;}
int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);}
int found(int x){return fa[x]==x?x:found(fa[x]);}
int dfs(int x,int p,int now){
    if(now==ed[x]+1)return p==num[x];
    int u=found(e[now].u),v=found(e[now].v),as=0;
    if(u!=v){
        fa[u]=v;
        as=dfs(x,p+1,now+1);
        fa[u]=u;
    }
    if(ed[x]-now+p>=num[x])as+=dfs(x,p,now+1);//jz
    return as;
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].w);
    sort(e+1,e+m+1,cmp);
    for(int i=1;i<=n;i++)fa[i]=i;
    int cnt=0;
    for(int i=1;i<=m;i++){
        if(e[i].w!=e[i-1].w)be[++tot]=i;
        if(e[i].w!=e[i+1].w)ed[tot]=i;
        if(find(e[i].u)!=find(e[i].v)){
            fa[fa[e[i].u]]=fa[e[i].v];
            num[tot]++;cnt++;
        }
    }
    if(cnt<n-1){printf("0");return 0;}
    for(int i=1;i<=n;i++)fa[i]=i;
    long long ans=1;
    for(int i=1;i<=tot;i++){
        ans=1ll*ans*dfs(i,0,be[i])%MOD;
        for(int j=be[i];j<=ed[i];j++)if(find(e[j].u)!=find(e[j].v)){
            fa[fa[e[j].u]]=fa[e[j].v];
        }
    }
    printf("%lld",ans);
    return 0;
}
View Code

 

posted @ 2017-12-29 09:42  ONION_CYC  阅读(364)  评论(0编辑  收藏  举报