zoukankan      html  css  js  c++  java
  • 【模版】生成树计数

    无向图生成树计数(Matrix-Tree)

    对于生成树的计数,一般采用矩阵树定理(Matrix-Tree 定理)来解决。

    Matrix-Tree 定理的内容为:对于已经得出的基尔霍夫矩阵,去掉其随意一行一列得出的矩阵的行列式,其绝对值为生成树的个数

    因此,对于给定的图 G,若要求其生成树个数,可以先求其基尔霍夫矩阵,然后随意取其任意一个 n-1 阶行列式,然后求出行列式的值,其绝对值就是这个图中生成树的个数

    • 度数矩阵 D[G]当$ i≠j$ 时,(D[i][j]=0),当$ i=j$ 时,(D[i][i] = degree(v_i))

    • 邻接矩阵 A[G](v_i)(v_j) 有边连接时,(A[i][j]=1),当 (v_i)(v_j) 无边连接时,(A[i][j]=0)

    • 基尔霍夫矩阵(Kirchhoff) K[G]:也称拉普拉斯算子,其定义为(K[G]=D[G]-A[G]),即:(K[i][j]=D[i][j]-A[i][j])

    取模

    • 图中节点的下标从0开始计数!
    • 不存在自环,允许存在重边
    • 求行列式参数为n,求生成树计数参数为n-1
    typedef long long ll;
    ll mod;
    
    const int N = 205;
    
    struct Matrix {
        ll mat[N][N];
        void init() {
            memset(mat,0,sizeof(mat));
        }
        void addEdge(int u,int v) {
            mat[u][v]--;
            mat[u][u]++;
        }
        ll det(int n){
            ll res=1;
            for(int i=0;i<n;++i){
                if(!mat[i][i]){
                    bool flag=false;
                    for(int j=i+1;j<n;++j){
                        if(mat[j][i]){
                            flag=true;
                            for(int k=i;k<n;++k) swap(mat[i][k],mat[j][k]);
                            res=-res;
                            break;
                        }
                    }
                    if(!flag) return 0;
                }
                for(int j=i+1;j<n;++j){
                    while(mat[j][i]){
                        ll t=mat[i][i]/mat[j][i];
                        for(int k=i;k<n;++k){
                            mat[i][k]=(mat[i][k]-t*mat[j][k])%mod;
                            swap(mat[i][k],mat[j][k]);
                        }
                        res=-res;
                    }
                }
                res*=mat[i][i];
                res%=mod;//模意义下的语句,不是模意义则不加
            }
            if(res<0) res+=mod;
            return res;
        }
    }ret;
    

    调用(求行列式):

    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
            scanf("%lld", &ret.mat[i][j]);
        }
    }
    printf("%lld
    ", ret.det(n));
    

    取逆元(mod为质数)

    • 图中节点的下标从0开始计数!
    • 不存在自环,允许存在重边
    • 求行列式参数为n,求生成树计数参数为n-1
    typedef long long ll;
    const ll mod = 998244353;
    
    const int N = 105;
    const int M = 1e4 + 5;
    
    ll inv(ll a) {
        if(a == 1)return 1;
        return inv(mod%a)*(mod-mod/a)%mod;
    }
    
    struct Matrix {
        ll mat[N][N];
        void init() {
            memset(mat,0,sizeof(mat));
        }
        void addEdge(int u,int v) {
            mat[u][v]--;
            mat[u][u]++;
        }
        ll det(int n) { //求行列式的值模上MOD,需要使用逆元
            for(int i = 0; i < n; i++)
                for(int j = 0; j < n; j++)
                    mat[i][j] = (mat[i][j]%mod+mod)%mod;
            ll res = 1;
            for(int i = 0; i < n; i++) {
                for(int j = i; j < n; j++)
                    if(mat[j][i]!=0) {
                        for(int k = i; k < n; k++)
                            swap(mat[i][k],mat[j][k]);
                        if(i != j)
                            res = (-res+mod)%mod;
                        break;
                    }
                if(mat[i][i] == 0) {
                    res = 0;//不存在(也就是行列式值为0)
                    break;
                }
                for(int j = i+1; j < n; j++) {
                    //int mut = (mat[j][i]*INV[mat[i][i]])%MOD;//打表逆元
                    ll mut = (mat[j][i]*inv(mat[i][i]))%mod;
                    for(int k = i; k < n; k++)
                        mat[j][k] = (mat[j][k]-(mat[i][k]*mut)%mod+mod)%mod;
                }
                res = (res * mat[i][i])%mod;
            }
            return res;
        }
    }ret;
    

    调用:

    ret.init();
    for (int i = 1; i <= m; i++) {
        scanf("%d%d%lld", &e[i].u, &e[i].v, &e[i].w);
        ret.addEdge(--e[i].u, --e[i].v);
        ret.addEdge(e[i].v, e[i].u);
    }
    ll tot = ret.det(n-1);
    if(tot == -1) {
        puts("0");
        continue;
    }
    

    不取模

    • 图中节点的下标从1开始计数!
    • 不存在自环,允许存在重边
    • 求行列式参数为n,求生成树计数参数为n-1
    typedef long long ll;
    
    const int N = 55;
    
    struct Matrix {
        ll mat[N][N];
        void init() {
            memset(mat, 0, sizeof mat);
        }
        ll gauss(int n) {
            ll res = 1;
            for (int i = 1; i <= n; i++) {
                for (int j = i + 1; j <= n; j++) {
                    while (mat[j][i]) {
                        ll t = mat[i][i] / mat[j][i];
                        for (int k = i; k <= n; k++)
                            mat[i][k] = (mat[i][k] - t * mat[j][k]);
                        swap(mat[i], mat[j]);
                        res = -res;
                    }
                }
                if(mat[i][i] == 0) return 0;
                res = res * mat[i][i];
            }
            if(res < 0) res = -res;
            return res;
        }
        void add(int u, int v) {
            mat[u][u]++;
            mat[v][v]++;
            mat[u][v]--;
            mat[v][u]--;
        }
    }ret;
    

    调用:

    ret.init();
    for (int i = 1; i <= m; i++) {
        int u, v;
        scanf("%d%d", &u, &v);
        ret.add(u, v);
    }
    printf("%lld
    ", ret.gauss(n-1));
    

    最小生成树计数(Kruskal+Matrix-Tree)

    用kruscal计算最小生成树时,每次取连接了两个不同联通块的最小的边。也就是先处理(d_1)(c_1)长度的边,再处理(d_2)(c_2)长度的边。长度相同的边无论怎么选,最大联通情况都是固定的。 分别对(c_i)长度的边产生的几个联通块计算生成树数量再乘起来,然后把这些联通块缩点,再计算(c_{i+1})长度的边。

    并查集(fa[i])是当前长度之前,节点所属的联通块,(ka[i])是当前长度的边连接后它在的联通块。

    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #include <vector>
    using namespace std;
    typedef long long ll;
    const int N=101;
    const int M=1001;
    ll n,m,p,ans;
    vector<int>gra[N];
    struct edge{
        int u,v,w;
    }e[M];
    int cmp(edge a,edge b){
        return a.w<b.w;
    }
    ll mat[N][N],g[N][N];
    ll fa[N],ka[N],vis[N];
    ll det(ll c[][N],ll n){
        ll i,j,k,t,ret=1;
        for(i=0;i<n;i++)
        for(j=0;j<n;j++) c[i][j]%=p;
        for(i=0; i<n; i++){
            for(j=i+1; j<n; j++)
                while(c[j][i]){
                    t=c[i][i]/c[j][i];
                    for(k=i; k<n; k++)
                        c[i][k]=(c[i][k]-c[j][k]*t)%p;
                    swap(c[i],c[j]);
                    ret=-ret;
                }
            if(c[i][i]==0)
                return 0L;
            ret=ret*c[i][i]%p;
        }
        return (ret+p)%p;
    }
    ll find(ll a,ll f[]){
        return f[a]==a?a:find(f[a],f);
    }
    void matrix_tree(){//对当前长度的边连接的每个联通块计算生成树个数
        for(int i=0;i<n;i++)if(vis[i]){//当前长度的边连接了i节点
            gra[find(i,ka)].push_back(i);//将i节点压入所属的联通块
            vis[i]=0;//一边清空vis数组
        }
        for(int i=0;i<n;i++)
        if(gra[i].size()>1){//联通块的点数为1时生成树数量是1
            memset(mat,0,sizeof mat);//清空矩阵
            int len=gra[i].size();
            for(int j=0;j<len;j++)
            for(int k=j+1;k<len;k++){//构造这个联通块的矩阵(有重边)
                int u=gra[i][j],v=gra[i][k];
                if(g[u][v]){
                    mat[k][j]=(mat[j][k]-=g[u][v]);
                    mat[k][k]+=g[u][v];mat[j][j]+=g[u][v];
                }
            }
            ans=ans*det(mat,gra[i].size()-1)%p;
            for(int j=0;j<len;j++)fa[gra[i][j]]=i;//缩点
        }
        for(int i=0;i<n;i++)
        {
            gra[i].clear();
            ka[i]=fa[i]=find(i,fa);
        }
    }
    int main(){
        while(scanf("%lld%lld%lld",&n,&m,&p),n){
            for(int i=0;i<m;i++){
                int u,v,w;
                scanf("%d%d%d",&u,&v,&w);
                u--;v--;
                e[i]=(edge){u,v,w};
            }
            sort(e,e+m,cmp);
            memset(g,0,sizeof g);
            ans=1;
            for(ll i=0;i<n;i++)ka[i]=fa[i]=i;
            for(ll i=0;i<=m;i++){//边从小到大加入
                if(i&&e[i].w!=e[i-1].w||i==m)//处理完长度为e[i-1].w的所有边
                    matrix_tree();//计算生成树
                ll u=find(e[i].u,fa),v=find(e[i].v,fa);//连的两个缩点后的点
                if(u!=v)//如果不是一个
                {
                    vis[v]=vis[u]=1;
                    ka[find(u,ka)]=find(v,ka);//两个分量在一个联通块里。
                    g[u][v]++,g[v][u]++;//邻接矩阵
                }
            }
            int flag=1;
            for(int i=1;i<n;i++)if(fa[i]!=fa[i-1])flag=0;
            printf("%lld
    ",flag?ans%p:0);//注意p可能为1,这样m=0时如果ans不%p就会输出1
        }
    }
    
  • 相关阅读:
    和2018年年初做管理系统的不同(vuex)
    项目中使用package-lock.json锁版本问题
    沟通协作:避免犯低级错误,开发前沟通清楚所有细节
    学会学习:高效学习方式(使用vscode-snippet有感)
    关于学习,避免没必要的熬夜的思考
    pc端布局的一点思考
    学习掌握一个新东西
    要想有价值,首先要乐于去解决每一个问题
    problem: 记一次聊天框的表情包弹框不显示的找问题过程
    abp-159,js最理想的继承——寄生组合式继承
  • 原文地址:https://www.cnblogs.com/Waldeinsamkeit/p/13450707.html
Copyright © 2011-2022 走看看