zoukankan      html  css  js  c++  java
  • 【暖*墟】#图论计数# 矩阵树定理的学习与练习

    【基本概念及实现】

     

    矩阵树定理用于求解图上生成树的个数。

     

    实现方式是:A为邻接矩阵,D为度数矩阵,则基尔霍夫(Kirchhoff)矩阵即为:K=D−A。

     

    得到K:记a为Kirchhoff矩阵,若存在E(u,v),则a[u][u]++,a[v][v]++,a[u][v]−−,a[v][u]−−。

    那么,a[i][i]为i点的度数,a[i][j]为 i,j之间边的条数的相反数,就构造出K矩阵了。

     

    • 这样构成的矩阵K的行列式的值,就为此图中生成树的个数。

     

    求解行列式的快速方法:使用高斯消元进行消元消出上三角矩阵,则有'对角线上的值的乘积=行列式的值'。

     

    一般而言求解生成树个数的题目数量会非常庞大,需要取模处理。

    有些情况下模数不为质数,因此不能直接在模意义下消元,需要使用辗转相除法。

     

    int Gauss(){
    
      int ans=1; for(int i=1;i<=tot;i++){
    
        for(int j=i+1;j<=tot;j++) //tot是总点数
    
              while(f[j][i]){ int t=f[i][i]/f[j][i];
    
            for(int k=i;k<=tot;k++)
    
                f[i][k]=(f[i][k]-t*f[j][k]%mod+mod)%mod,

    swap(f[i][k],f[j][k]); ans
    =-ans; //辗转相除法 } ans=(ans*f[i][i])%mod; } return (ans+mod)%mod; //注意ans可能为负数 }

     

    变元矩阵树定理:求所有生成树的总边积的和。

    • 和矩阵树的求法类似,a[i][i]记录总的边权和,a[i][j]记录i,j之间边权的相反数。

     


     

    【例题1】P4111 小Z的房间

     

    • 你突然有了一个大房子,房子里面有一些房间。房子可以看做是一个包含n×m个格子的矩形,
    • 每个格子是一个房间或者是一个柱子。在一开始的时候,相邻的格子之间都有墙隔着。
    • 你想要打通一些相邻房间的墙,使得所有房间能够互相到达。
    • 在此过程中,你不能把房子给打穿,或者打通柱子(以及柱子旁边的墙)。
    • 同时,你不希望在房子中有小偷的时候会很难抓,所以你希望任意两个房间之间都只有一条通路。
    • 现在,你希望统计一共有多少种可行的方案。答案对10^9 取模。1<=n,m<=9。

     

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<queue>
    #include<string>
    #include<cmath>
    using namespace std;
    typedef long long ll;
    
    //【p4111】小Z的房间 // 求图中生成树(所有节点全部连通的树)的数量
    
    //【矩阵树定理】Kirchhoff矩阵的行列式的值,就是图中生成树的个数。
    
    // 注意:不能加入柱子的点。加边时,只枚举(i,j)上方和左方的点,可以避免重复。
    
    void reads(int &x){ //读入优化(正负整数)
        int fx_=1;x=0;char s=getchar();
        while(s<'0'||s>'9'){if(s=='-')fx_=-1;s=getchar();}
        while(s>='0'&&s<='9'){x=(x<<3)+(x<<1)+s-'0';s=getchar();}
        x*=fx_; //正负号
    }
    
    const int mod=1e9; int n,m,f[109][109],id[19][19],tot=0;
    
    void add(int u,int v){ f[u][v]--,f[v][u]--,f[u][u]++,f[v][v]++; }
    
    int Gauss(){
      int ans=1; for(int i=1;i<tot;i++){
        for(int j=i+1;j<tot;j++) //tot是总点数
          while(f[j][i]){ int t=f[i][i]/f[j][i];
            for(int k=i;k<tot;k++) 
                f[i][k]=(f[i][k]-1LL*t*f[j][k]%mod+mod)%mod;
            std::swap(f[i],f[j]),ans=-ans; //辗转相除法
          } ans=(1LL*ans*f[i][i])%mod;
      } return (ans+mod)%mod; //注意ans可能为负数
    }
    
    char ss[19][19];
    
    int main(){
        reads(n),reads(m); for(int i=1;i<=n;i++) scanf("%s",ss[i]+1);
        for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) 
            if(ss[i][j]=='.') id[i][j]=++tot; //给每个非墙的点(构成树的点)编号
        for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) if(ss[i][j]=='.'){
            if(id[i-1][j]) add(id[i][j],id[i-1][j]); //向上方连边
            if(id[i][j-1]) add(id[i][j],id[i][j-1]); //向左边连边
        } printf("%d
    ",Gauss()); //矩阵树定理求图中生成树的数量
    }
    【p4111】小Z的房间 // 求图中生成树(所有节点全部连通的树)的数量

     

    细节问题就是:

    for(int k=i;k<tot;k++)

        f[i][k]=(f[i][k]-1LL*t*f[j][k]%mod+mod)%mod;

    std::swap(f[i],f[j]),ans=-ans; //辗转相除法

    在本地编译不过,所以也可以写成:

    for(int k=i;k<tot;k++)

        f[i][k]=(f[i][k]-1LL*t*f[j][k]%mod+mod)%mod,

        swap(f[i][k],f[j][k]); ans=-ans; //辗转相除法


     

    【例题2】P4336 黑暗前的幻想乡

    • n个点、n−1种颜色,某种颜色可以涂某些边。
    • 求图中n-1条边都不同色的生成树的数量 % 10^9+7 。

    【容斥原理】合法方案数=总生成树方案数-缺一种颜色的方案数+缺两种颜色的方案数……

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<queue>
    #include<string>
    #include<cmath>
    using namespace std;
    typedef long long ll;
    
    //【p4336】黑暗前的幻想乡 // 矩阵树定理 + 容斥原理
    
    //n个点、n−1种颜色,某种颜色可以涂某些边。
    //求图中n-1条边都不同色的生成树的数量 % 10^9+7 。
    
    //【矩阵树定理】Kirchhoff矩阵的行列式的值,就是图中生成树的个数。
    
    //【容斥原理】合法方案数=总生成树方案数-缺一种颜色的方案数+缺两种颜色的方案数……
    
    void reads(int &x){ //读入优化(正负整数)
        int fx_=1;x=0;char s=getchar();
        while(s<'0'||s>'9'){if(s=='-')fx_=-1;s=getchar();}
        while(s>='0'&&s<='9'){x=(x<<3)+(x<<1)+s-'0';s=getchar();}
        x*=fx_; //正负号
    }
    
    const int mod=1e9+7; int n,m,f[19][19],anss=0;
    
    void add(int u,int v){ f[u][v]--,f[v][u]--,f[u][u]++,f[v][v]++; }
    
    int Gauss(){
      int ans=1; for(int i=1;i<n;i++){
        for(int j=i+1;j<n;j++) //n是总点数
          while(f[j][i]){ int t=f[i][i]/f[j][i];
            for(int k=i;k<n;k++) 
              f[i][k]=(f[i][k]-1LL*t*f[j][k]%mod+mod)%mod,
              swap(f[i][k],f[j][k]); ans=-ans; //辗转相除法
          } ans=(1LL*ans*f[i][i])%mod;
      } return (ans+mod)%mod; //注意ans可能为负数
    }
    
    vector < pair<int,int> > q[19]; 
    
    int main(){
        reads(n); for(int i=1;i<n;i++)
          { reads(m); for(int j=1,x,y;j<=m;j++)
              reads(x),reads(y),q[i].push_back(make_pair(x,y)); }
        for(int i=0;i<(1<<(n-1));i++){ //枚举某种修建状态【穷举+容斥】
            int cnt=0; memset(f,0,sizeof(f));
            for(int j=1;j<n;j++) if(i&(1<<(j-1))){
                for(int k=0;k<q[j].size();k++) //如果状态i中修了边j
                    add(q[j][k].first,q[j][k].second); //将边放入矩阵K
                cnt++; //此状态i下,修建的边数
            } if((n-cnt)&1) anss=(anss+Gauss())%mod;
              else anss=(anss-Gauss()+mod)%mod;
        } cout<<anss<<endl; //最终得到的答案:
    }
    【p4336】黑暗前的幻想乡 // 矩阵树定理 + 容斥原理

                                                   ——时间划过风的轨迹,那个少年,还在等你。

  • 相关阅读:
    MySql
    027 mysql
    navicat
    基于阿里云资源的分布式部署方案
    translate(50%,50%) 实现水平垂直居中
    SSH2 协议详解
    DNS服务配置篇 巴哥学Server2003
    Java 8 后的新功能梳理
    IBM Cognos BI 最佳实践系列 网站地址
    jsf2.0 入门视频 教程
  • 原文地址:https://www.cnblogs.com/FloraLOVERyuuji/p/10524226.html
Copyright © 2011-2022 走看看