bzoj1016-JSOI2008 最小生成树计数 最小生成树 dfs/matrix-tree定理

Posted paul120090105(AFO)

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了bzoj1016-JSOI2008 最小生成树计数 最小生成树 dfs/matrix-tree定理相关的知识,希望对你有一定的参考价值。

  • 题面描述
    • 现在给出了一个简单无向加权图。你不满足于求出这个图的最小生成树,而希望知道这个图中有多少个不同的最小生成树。(如果两颗最小生成树中至少有一条边不同,则这两个最小生成树就是不同的)。由于不同的最小生成树可能很多,所以你只需要输出方案数对(31011)的模就可以了。
  • 输入格式
    • 第一行包含两个数,(n)(m),其中(1leq nleq 10^2, 1leq mleq 10^3) 表示该无向图的节点数和边数。每个节点用([1,n])的整数编号。接下来的(m)行,每行包含两个整数:(a, b, c),表示节点(a, b)之间的边的权值为(c),其中(1leq cleq 10^9)。数据保证不会出现自回边和重边。
    • 注意:具有相同权值的边不会超过(10)条。
  • 输出格式
    • 输出不同的最小生成树有多少个。你只需要输出数量对(31011)的模就可以了。
  • 题解
    • 引理
      • 对于一张给定的无向图,权值相同的边在该无向图的最小生成树中数量相同,连接的集合相同(作用相同)
        • 证明:(数学归纳法)
          • 假设(kruskal)过程中当加入完一种权值的边,要加入下一种权值(x)的边时,对于权值为(x)的边所面对的集合相同。因为集合相同,故加入的边数相同。
          • 假设加入完权值为(x)的边后集合为(S),边数为(cnt),权值为(x)的边加入到最小生成树的边集的集合为(in_x),没有加入边集的集合为(out_x)
          • 想要让(S)产生变化,有三种情况:
            • (ein in_x)(in_x) 中删除加入到(out_x)
              • 但根据(kruskal)的算法,如果 存在 (e') 使得两个原本不交的点集合并成一个点集,则该边(e')必然加入(in_x)。因此(e)不可能从(in_x)中删除。
            • (ein out_x)(out_x) 中删除加入到(in_x)中。
              • 同理,如果该边能够加入(in_x),必然已经存在于(in_x)中了,因此(forall ein out_x)都不可能加入(in_x)
            • (e_1in in_x)(in_x) 中删除加入到(out_x)中,将(e_2in out_x)(out_x)中删除加入到(in_x)中。
              • 假设如此操作能够改变(S)。设(e_1)原先连接的两个点集为(S_{1,1},S_{1,2})(e_2)原先连接的两个点集为(S_{2,1},S_{2,2})
              • 如果(S_{1,1},S_{1,2})(S_{2,1},S_{2,2})不相同,(e_2)想要加入(in_x)中,必然要满足(S_{2,1},S_{2,2})两个点集在原最小生成树上是两个不同的点集,而如果是这样的话,(e_2)必然已经被加入(in_x)中,而不可能存在于(out_x)中。
              • 如果(S_{1,1},S_{1,2})(S_{2,1},S_{2,2})相同,如此操作对(S)无影响。
          • 因此对于权值(x)的边加入后的(S'),无论权值(x)的边如何取,一定全部相同
          • 证毕
    • 因此我们可以将每种不同权值的边独立开来考虑,这时就有两种想法:
      • 先做一遍完整的(kruskal)求得每种权值的边在最小生成树出现的条数。再对于权值(x)的边我通过(dfs)求得所有合法加入方案,共(cnt_x)种。再用乘法原理的(ans=prod_{x}cnt_x \%mod?)
      • 在做(kruskal)时对于权值(x)的边单独考虑,将加入权值(x)的边前的图缩点,连上所有权值(x)的边,跑(matrix-tree)定理,跑出所有生成树方案(cnt_x)。再用乘法原理的(ans=prod_{x}cnt_x \%mod)
        • 注意:将权值(x)的边连进(matrix-tree)(mat)中后,这个缩过点的图不一定连通,要加一些桥边( 桥边 对 生成树方案个数 无贡献)保证图连通

(dfs)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN=1e2+5;
const int MAXM=2e3+5;
const int mod=31011;
struct rec{
    int u,v,w;
} ed[MAXM];
int cnt[MAXM];
int n,m,tot,ans,sum;
int fa[MAXN];
bool use[MAXM];
bool cmp(rec a,rec b){ return a.w<b.w; }
int find(int x){ return fa[x]==x?x:find(fa[x]); }
void uion(int x,int y){ fa[find(x)]=find(y); }
void dfs(int stp,int l,int r,int now,int cnt){
    if (now==r+1){
//      cout<<stp<<" "<<cnt<<endl;
        if (stp==cnt) sum=(sum+1)%mod;
        return;
    }
    int u=ed[now].u,v=ed[now].v;
    int fu=find(u),fv=find(v);
    if (fu!=fv){
        fa[fu]=fv;
        dfs(stp+1,l,r,now+1,cnt);
        fa[fu]=fu; fa[fv]=fv;
    }
    dfs(stp,l,r,now+1,cnt);
}
int main(){
    scanf("%d%d",&n,&m);
    for (int i=1;i<=m;i++){
        int u,v,w; scanf("%d%d%d",&u,&v,&w);
        ed[i]=(rec){u,v,w};
    }
    for (int i=1;i<=n;i++) fa[i]=i;
    sort(ed+1,ed+m+1,cmp);
    int res=0;
    for (int i=1;i<=m;i++){
        if (ed[i].w!=ed[i-1].w) res++;
        int u=ed[i].u,v=ed[i].v;
        if (find(u)==find(v)) continue;
        cnt[res]++; uion(u,v); tot++;
    }
    if (tot!=n-1) return printf("0
"),0;
    for (int i=1;i<=n;i++) fa[i]=i;
    ans=res=1;
    for (int l=1,r=1;l<=m;l=r+1,r++,res++){
        while (ed[l].w==ed[r].w) r++;
        r--; sum=0;
        dfs(0,l,r,l,cnt[res]);
//      cout<<l<<" "<<r<<" "<<cnt[res]<<" "<<sum<<endl;
        ans=ans*sum%mod;
        for (int i=l;i<=r;i++){
            int u=ed[i].u,v=ed[i].v;
            if (find(u)==find(v)) continue;
            uion(u,v);
        }
    }
    printf("%d
",ans);
    return 0;
}

(matrix-tree)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN=1e2+5;
const int MAXM=1e3+5;
const int mod=31011;
struct rec{
    int u,v,w;
} ed[MAXM];
int n,m,ans=1,tot;
int fa[MAXN];
int use[MAXN];
int mat[MAXN][MAXN];
int ffa[MAXN];
bool cmp(rec a,rec b){ return a.w<b.w; }
int find(int x){ return fa[x]==x?x:fa[x]=find(fa[x]); }
void uion(int x,int y){ fa[find(x)]=find(y); }
int ffind(int x){ return ffa[x]==x?x:ffa[x]=ffind(ffa[x]); }
void fuion(int x,int y){ ffa[ffind(x)]=ffind(y); }
int main(){
    scanf("%d%d",&n,&m);
    for (int i=1;i<=m;i++){
        int u,v,w; scanf("%d%d%d",&u,&v,&w);
        ed[i]=(rec){u,v,w};
    }
    sort(ed+1,ed+m+1,cmp);
    for (int i=1;i<=n;i++) fa[i]=i;
    for (int l=1,r=1;l<=m;l=r+1,r++){
        while (ed[l].w==ed[r].w) r++;
        r--;
//      cout<<l<<" "<<r<<endl;
        memset(mat,0,sizeof(mat));
        memset(use,0,sizeof(use));
        bool flag=0;
        int cnt=0;
        for (int i=l;i<=r;i++){
            int u=ed[i].u,v=ed[i].v;
            int fu=find(u),fv=find(v);
            if (!use[fu]) use[fu]=++cnt;
            if (!use[fv]) use[fv]=++cnt;
        }
        for (int i=1;i<=cnt;i++) ffa[i]=i;
        for (int i=l;i<=r;i++){
            int u=ed[i].u,v=ed[i].v;
            int fu=find(u),fv=find(v);
            if (fu!=fv) flag=1;
            fu=use[fu]; fv=use[fv];
            fuion(fu,fv);
            mat[fu][fv]--; mat[fv][fu]--;
            mat[fu][fu]++; mat[fv][fv]++;
        }
        for (int i=2;i<=cnt;i++){
            int fu=ffind(i-1),fv=ffind(i);
            if (fu!=fv){
                fuion(i-1,i);
                mat[fu][fv]--; mat[fv][fu]--;
                mat[fu][fu]++; mat[fv][fv]++;
            }
        }
        cnt--;
/*      for (int i=1;i<=cnt;i++){
            for (int j=1;j<=cnt;j++) cout<<mat[i][j]<<" ";
            cout<<endl;
        }
        cout<<endl;
*/      int ret=1;
        for (int i=1;i<=cnt;i++){
            int pos=i;
            for (int j=i+1;j<=cnt;j++){
                if (mat[j][i]) pos=j;
            }
            for (int j=1;j<=cnt;j++) swap(mat[i][j],mat[pos][j]);
            if (pos!=i) ret=-ret;
            for (int j=i+1;j<=cnt;j++){
                while (mat[j][i]){
                    int t=mat[j][i]/mat[i][i];
                    for (int k=1;k<=cnt;k++){
                        mat[j][k]=(mat[j][k]-t*mat[i][k])%mod;
                    }
                    if (!mat[j][i]) break;
                    ret=-ret;
                    for (int k=1;k<=cnt;k++) swap(mat[j][k],mat[i][k]);
                }
            }
            ret=ret*mat[i][i]%mod;
        }       
/*      for (int i=1;i<=cnt;i++){
            for (int j=1;j<=cnt;j++) cout<<mat[i][j]<<" ";
            cout<<endl;
        }
*/      ret=(ret%mod+mod)%mod;
//      cout<<ret<<endl;
        if (flag) ans=ans*ret%mod;
        for (int i=l;i<=r;i++){
            int u=ed[i].u,v=ed[i].v;
            int fu=find(u),fv=find(v);
            if (fu==fv) continue;
            uion(u,v); tot++;
        }
    }
    if (tot!=n-1) return printf("0
"),0;
    printf("%d
",ans);
    return 0;
}

以上是关于bzoj1016-JSOI2008 最小生成树计数 最小生成树 dfs/matrix-tree定理的主要内容,如果未能解决你的问题,请参考以下文章

bzoj 1016 [JSOI2008]最小生成树计数

BZOJ-1016: [JSOI2008]最小生成树计数 (kruscal+搜索)

bzoj1016: [JSOI2008]最小生成树计数

BZOJ1016: [JSOI2008]最小生成树计数 深搜+并查集

BZOJ1016:[JSOI2008]最小生成树计数——题解

bzoj1016[JSOI2008]最小生成树计数