zoukankan      html  css  js  c++  java
  • [LuoguP4208][JSOI2008]最小生成树计数(最小生成树+矩阵树定理)

    [LuoguP4208][JSOI2008]最小生成树计数

    题面

    现在给出了一个简单无向加权图。你不满足于求出这个图的最小生成树,而希望知道这个图中有多少个不同的最小生成树。(如果两颗最小生成树中至少有一条边不同,则这两个最小生成树就是不同的)。由于不同的最小生成树可能很多,所以你只需要输出方案数对31011的模就可以了。数据保证不会出现自回边和重边。注意:具有相同权值的边不会超过10条。

    分析

    引理:同一个图的所有最小生成树中,边权相等的边数量相等

    考虑Kruskal算法的过程,我们会按边权依次加入两端点不属于同一连通块的边。边权相同的边是连续加入的,因此操作等价于加入所有边权相同的边,然后消除所有简单环,不同的消环方式对应不同的生成树。消除一个简单环等价于删掉环中一条边。所以,无论怎么消环,最后剩余的总边数一定。

    根据这个引理,我们可以求出每一种边权的选择方案,再乘起来得到答案。对于每种边权,我们将最小生成树上非该边权的边加到一个新图上,然后把每个连通块缩成一个点。之后将原图上所有边权为该值的边都加在新图上。新图的生成树个数即为答案,用Matrix-Tree定理求解。(其实这里的生成树个数和引理证明里提到的消环方案数是等价的)

    可以证明复杂度为(Theta(n^3))

    代码

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define maxn 100 
    #define maxm 1000
    #define mod 31011
    using namespace std;
    typedef long long ll;
    int n,m;
    struct DSU{
        int fa[maxn+5];
        int find(int x){
            if(fa[x]==x) return x;
            else return fa[x]=find(fa[x]);
        }
        void merge(int x,int y){
            fa[find(x)]=find(y);
        }
        void ini(int n){
            for(int i=1;i<=n;i++) fa[i]=i;
        } 
    };
    struct edge{
        int from;
        int to;
        int len;
        bool on_tree; 
        friend bool operator < (edge p,edge q){
            return p.len<q.len;
        }
    }G[maxm+5];
    int cnt=0;
    int tlen[maxn+5];//因为不同MST中边权相等的边数量相等,按边权给边分类 
    void kruskal(){
        static DSU S;
        S.ini(n);
        sort(G+1,G+1+m);
        for(int i=1;i<=m;i++){
            int x=G[i].from,y=G[i].to;
            if(S.find(x)!=S.find(y)){
                S.merge(x,y);
                G[i].on_tree=1;
                tlen[++cnt]=G[i].len;
            }
        }
        sort(tlen+1,tlen+1+cnt);
        cnt=unique(tlen+1,tlen+1+cnt)-tlen-1; 
    }
    
    ll g[maxn+5][maxn+5];
    void add_edge(int u,int v){
        g[u][u]=(g[u][u]+1)%mod;
        g[u][v]=(g[u][v]-1)%mod;
        g[v][v]=(g[v][v]+1)%mod;
        g[v][u]=(g[v][u]-1)%mod;
    }
    ll gauss(int n){
        ll ans=1;
        for(int i=1;i<n;i++){
            for(int j=i+1;j<n;j++){
                while(g[j][i]){
                    int d=g[i][i]/g[j][i];
                    for(int k=1;k<n;k++) g[i][k]=(g[i][k]-g[j][k]*d%mod+mod)%mod;
                    for(int k=1;k<n;k++) swap(g[i][k],g[j][k]);
                    ans=mod-ans; 
                }
            }
            if(g[i][i]==0) return 0;
            ans=ans*g[i][i]%mod;
        }	
        return (ans%mod+mod)%mod; 
    }
    ll calc(int len){
        static int bel[maxn+5];
        static DSU S;
        for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) g[i][j]=0;
        S.ini(n);
        for(int i=1;i<=m;i++){//把最小生成树上不等于len的边构成的连通块缩点 
            if(G[i].len!=len&&G[i].on_tree) S.merge(G[i].from,G[i].to);
        }
        int ptr=0;
        for(int i=1;i<=n;i++) if(S.find(i)==i) bel[i]=++ptr;
        for(int i=1;i<=n;i++) bel[i]=bel[S.find(i)];//要重标号再求生成树 
        for(int i=1;i<=m;i++){//对等于len的边构成的新图求生成树数量 
            if(G[i].len==len) add_edge(bel[G[i].from],bel[G[i].to]);
        } 
        return gauss(ptr);
    }
    int main(){
    //	int u,v,w;
        scanf("%d %d",&n,&m);
        for(int i=1;i<=m;i++) scanf("%d %d %d",&G[i].from,&G[i].to,&G[i].len); 
        kruskal();
        ll ans=1;
        for(int i=1;i<=cnt;i++){
            ans=ans*calc(tlen[i])%mod;	
        }
        printf("%lld
    ",ans);
    }
    
  • 相关阅读:
    作业20181127-1 附加作业 软件工程原则的应用实例分析
    20181120-1 每周例行报告
    20181113-2 每周例行报告
    获奖感言
    作业 20181030-4 每周例行报告
    20181023-3 每周例行报告
    Weekly 13
    Weekly 10
    Weekly 11
    weekly 8
  • 原文地址:https://www.cnblogs.com/birchtree/p/13377158.html
Copyright © 2011-2022 走看看