矩阵树定理(生成树计数)学习笔记

度数矩阵

对于一个图 G G G,定义其度数矩阵为 D ( G ) D(G) D(G). D ( G ) D(G) D(G)是一个 n ∗ n n*n nn大小的对角线矩阵.
对角线上元素 d ( i , i ) d_{(i,i)} d(i,i)为顶点 i i i的度数。(非对角线上的元素 d ( i , j ) d_{(i,j)} d(i,j)为0)

邻接矩阵

对于一个图 G G G,定义其临接矩阵为 A ( G ) A(G) A(G).
a ( i , j ) a_{(i,j)} a(i,j) v i v_i vi v j v_j vj之间邻接的边数(注意可以有重边,即大于1)

Kirchoff矩阵树定理

K i r c h o f f 为 一 个 n ∗ n 的 矩 阵 Kirchoff为一个n*n的矩阵 Kirchoffnn: D ( G ) − A ( G ) D(G)-A(G) D(G)A(G)
即对于 ∀ i , j , K i , j = D i , j − A i , j ∀i,j,K_{i,j}=D_{i,j}-A_{i,j} i,j,Ki,j=Di,jAi,j
定理:对于一个图 G G G,其生成树个数为该Kirchoff矩阵行列式的值。
证明略过,会用即可。
用高斯消元法即可求得行列式的值。
模板如下(借用了洛谷某位大佬的):

void add(int u,int v) {
    
    
    --a[u][v],--a[v][u],++a[u][u],++a[v][v];
}
ll Gauss(int n) {
    
    
    ll ans=1;
    for(int i=1;i<=n;++i) {
    
    
        for(int k=i+1;k<=n;++k) {
    
    
            while(a[k][i]) {
    
    
                ll d=a[i][i]/a[k][i];
                for(int j=i;j<=n;++j) a[i][j]=(a[i][j]-1LL*d*a[k][j]%mod+mod)%mod;
                std::swap(a[i],a[k]),ans=-ans;
            }
        }
        ans=1LL*ans*a[i][i]%mod,ans=(ans+mod)%mod;
    }
    return ans;
}

注意调用Gauss函数时传入的是n-1.

例题传送门:
第一题
附上这位大佬的代码(我比较懒就没敲了):
大佬传送门

#include <cstdio>
#include <algorithm>

const int N=15,M=105;
const int mod=1e9;
char s[N][N];
int n,m,a[M][M],id[N][N];

void add(int u,int v) {
    
    
	--a[u][v],--a[v][u],++a[u][u],++a[v][v];
}
int Gauss(int n) {
    
    
	int ans=1;
	for(int i=1;i<=n;++i) {
    
    
		for(int k=i+1;k<=n;++k) {
    
    
			while(a[k][i]) {
    
    
				int d=a[i][i]/a[k][i];
				for(int j=i;j<=n;++j) a[i][j]=(a[i][j]-1LL*d*a[k][j]%mod+mod)%mod;
				std::swap(a[i],a[k]),ans=-ans;
			}
		}
		ans=1LL*ans*a[i][i]%mod,ans=(ans+mod)%mod;
	}
	return ans;
}
int main() {
    
    
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;++i) scanf("%s",s[i]+1);
	int idx=0;
	for(int i=1;i<=n;++i) for(int j=1;j<=m;++j) if(s[i][j]=='.') id[i][j]=++idx;
	for(int i=1;i<=n;++i) for(int j=1;j<=m;++j) if(s[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\n",Gauss(idx-1));
	return 0;
}

第二题
对于二进制每一位拆位计算贡献即可。

#include <bits/stdc++.h>
using namespace std;
#define MAXN 300050
#define ll long long

const int N=105,M=105;
const ll mod=998244353;
int n,m;
ll a[N][N];
ll qpow(ll x,ll y)
{
    
    
    ll res=1;
    while(y)
    {
    
    
        if(y&1)res=res*x%mod;
        x=x*x%mod;
        y>>=1;
    }
    return res;
}
ll inv(ll x)
{
    
    
    return qpow(x,mod-2);
}
void add(int u,int v) {
    
    
    --a[u][v],--a[v][u],++a[u][u],++a[v][v];
}
ll Gauss(int n) {
    
    
    ll ans=1;
    for(int i=1;i<=n;++i) {
    
    
        for(int k=i+1;k<=n;++k) {
    
    
            while(a[k][i]) {
    
    
                ll d=a[i][i]/a[k][i];
                for(int j=i;j<=n;++j) a[i][j]=(a[i][j]-1LL*d*a[k][j]%mod+mod)%mod;
                std::swap(a[i],a[k]),ans=-ans;
            }
        }
        ans=1LL*ans*a[i][i]%mod,ans=(ans+mod)%mod;
    }
    return ans;
}
int d[N][N][32];
int main() {
    
    
    int t;
    //freopen("E://tt.txt","r",stdin);
    cin>>t;
    while(t--)
    {
    
    
        memset(d,0, sizeof(d));
        memset(a,0, sizeof(a));
        scanf("%d%d",&n,&m);
        for(int i=1;i<=m;i++)
        {
    
    
            int u,v,w;
            scanf("%d%d%d",&u,&v,&w);
            for(int j=0;j<30;j++)
            {
    
    
                if(w&(1ll<<j))
                    d[u][v][j]++,d[v][u][j]++;
            }
            add(u,v);
        }
        ll all=Gauss(n-1);
        ll ans=0;
        for(int j=0;j<30;j++)
        {
    
    
            memset(a,0, sizeof(a));
            for(int i=1;i<=n;i++)
                for(int k=i+1;k<=n;k++)
                {
    
    
                    if(d[i][k][j]) {
    
    
                        for (int c = 0; c < d[i][k][j]; c++)
                            add(i, k);
                    }
                }
            ans+=(1ll<<j)*Gauss(n-1)%mod;
            ans%=mod;
        }
        ans=ans*inv(all)%mod;
        cout<<ans<<endl;
    }
}

第三题

容斥即可,建议自己手写关于这个组合数的式子证明一下结论的正确性:
a n s = ∑ i = 1 n ( − 1 ) i − 1 F ( n + 1 − i ) ans=\sum_{i=1}^{n}(-1)^{i-1}F(n+1-i) ans=i=1n(1)i1F(n+1i)
F ( i ) F(i) F(i)表示最多 i i i个公司参与修建的生成树个数.

#include <bits/stdc++.h>
using namespace std;
const int N=18,M=18;
const long long mod=1e9+7;
#define ll long long
ll n,a[M][M],id[N][N];
int m[N];
int v[N][N*N][2];
inline void add(int u,int v) {
    
    
    --a[u][v],--a[v][u],++a[u][u],++a[v][v];
}
int D[30];
inline int Gauss(int n) {
    
    
    int ans=1;
    for(register  int i=1;i<=n;++i) {
    
    
        for(register  int k=i+1;k<=n;++k) {
    
    
            while(a[k][i]) {
    
    
                int d=a[i][i]/a[k][i];
                for(register  int j=i;j<=n;++j) a[i][j]=(a[i][j]-1LL*d*a[k][j]%mod+mod)%mod;
                std::swap(a[i],a[k]),ans=-ans;
            }
        }
        ans=1LL*ans*a[i][i]%mod,ans=(ans+mod)%mod;
    }
    return ans;
}
int main() {
    
    
    scanf("%lld",&n);n--;
    for(int i=0;i<=n;i++)D[i]=1<<i;
    for(int i=1;i<=n;++i) {
    
    
        scanf("%lld",&m[i]);
        for(int j=0;j<m[i];j++)
        {
    
    
            int u,w;scanf("%d%d",&u,&w);
            v[i][j][0]=u;
            v[i][j][1]=w;
        }
    }
    int all=1<<(n);
    long long ans=0;
    for(register int i=1;i<all;++i)
    {
    
    
        memset(a,0, sizeof(a));
        int cnt=0;
        for(register int j=1;j<=n;++j)
        {
    
    
            if(i&D[j-1])
            {
    
    
                cnt++;
                for(register int k=0;k<m[j];++k)
                {
    
    
                    add(v[j][k][0],v[j][k][1]);
                }
            }
        }
        if((n-cnt)&1)
            ans=(ans-Gauss(n)+mod)%mod;
        else
            ans=(ans+Gauss(n))%mod;
    }
    cout<<ans<<endl;
}

猜你喜欢

转载自blog.csdn.net/weixin_43353639/article/details/107876406