矩陣樹定理——矩陣樹不是樹


先掛一個\(link\)

1、前置技能

\(In \ \ fact\),矩陣樹跟樹……嚴格意義上講,並沒有什么很大的關系,因為這個定理是基於圖的,而不是基於樹的。而對於這個定理,我們需要一系列前置操作:

一、對於矩陣的一堆定義:

\(G\)是一張無向圖:

\(D_{I,j}\)表示為度數矩陣,其中\(D_{i,i}\)記錄第\(I\)個節點的度數。

\(A_{I,j}\)表示為鄰接矩陣,其中\(A_{i,j}\)記錄這兩點之間連了多少條邊。

\(K_{I,j}\)稱為“基爾霍夫矩陣”\((\color{cyan}{Kirchhoff})\),而基爾霍夫矩陣的定義式為:$$\mathcal{K \ \ = \ \ D \ \ - \ \ A}$$

以上矩陣顯然都會是\(N \times N\)的qwq。

下圖是從網上扒翻出來的例子QAQ

二、對於行列式的一堆知識:

對於\(N\)階行列式\(det(A)\)“主子式”,可以理解為是$A_{i,i} \ \ i \in [1,n] $的余子式。

而對於一個行列式,我們要求它的值,可以根據其定義\(N!\)算出,用到的式子是這個:

$$\sum\limits_{}^{}{(-1)^k\prod\limits_{1}^{n}{a_{i,b_i}}} \ \ = \ \ det(A) $$,

其中\(b_1\)~\(b_n\)\(1\)~\(n\)的一種排列。

顯然的是,這個算法是\(O(N!)\)的,所我們並不能這么做。那我們可以根據其性質展開類似高斯消元一樣的算法,使其成為倒三角,然后對角線相乘\(over\).

int I,  j,  k, ans ;
int Gauss_work()
{
    ans = 1;
    for(i = 1; i < n; i ++)
    {
        for(j = i + 1; j < n; j ++)
            while(f[j][i]){
               t = f[i][i] / f[j][i];
                for(k = i; k < n; k ++)
                    f[i][k] = (f[i][k] - t * f[j][k] + mod) % mod;
                swap(f[i], f[j]);
                ans = - ans;
            }
        ans = (ans * f[i][i]) % mod;
    }
    return (ans + mod) % mod;
}

還是老套路,\(i\)枚舉主對角線上的第幾個第幾個元素,\(j\)枚舉剩下的行,然后和每一行輾轉相除\(qwq\),在這時\(k\)的枚舉用來按位加減。

這個玄學的代碼,為了防止出現double,所以采取輾轉相除的方式,具體的輾轉相除是這一段代碼

while(f[j][i]){
		t = f[i][i] / f[j][i] ;
		for(k = i; k < n; k ++){
	        	f[i[k] = f[i][k] - t * f[j][k] ;
		}
		swap(f[j], f[i]) ;
		ans = -ans ;
	}

這樣既保證了會消成\(0\),又可以不出\(double\),而我們需要注意在最后,\(ans = -ans\),因為對換一行或者一列使得其值取反\(qwq\)

2、那么矩陣樹該登場啦!

根據基爾霍夫矩陣,我們隨意取它的任意一個\(n - 1\)階主子式(可證明對於任意的\(i\)是等價的),然后求出主子式的值,得到的就是在這個圖中生成樹的數量

然而證明……我並不會證明……我怎么這么弱啊……

放心吧,等到今年暑假結束之前,我一定要回來完善這篇博客的!

擴展定理:

也叫做“變元矩陣樹定理”,如果我們把鄰接矩陣變成邊權矩陣,即\(A_{i,j}\)表示\(i,j\)兩條邊之間的邊權,\(D_{i,i}\)表示與第\(i\)個節點的相連的邊的邊權和,那么我們可以得到基爾霍夫矩陣就是所有生成樹中的邊權之積的和

也就是$$\mathcal{K \ \ = \ \ D \ \ - \ \ A } $$ $$\mathcal{det(K) = \sum\limits_{T}^{}{\prod\limits_{I = 1}^{n-1}{w_i}}}$$

先把朴素矩陣樹定理的\(code\)撂這兒吧

#include <cstdio>
#include <cstring>
#include <iostream>
#define il inline
#define MAXN 2225
#define ll long long 

using namespace std ;
ll N, T, M, ans = 1;
ll K[MAXN][MAXN] ;
ll a, b, i, j, k, t ;

il ll gauss_work(){
    for( i = 1; i < N ; i ++){
        for(j = i + 1; j < N ; j ++){
            while(K[j][i]){
                t = K[i][i] / K[j][i] ;
                for(k = i; k < N; k ++)
                    K[i][k] = K[i][k] - t * K[j][k] ;
                swap(K[i], K[j]) ;
                ans = -ans ; 
            }
        }
        ans *= K[i][i] ;
    }
    return ans ;
}
int main(){
     cin >> T ;
     while(T --){
     	ans = 1 ;
        scanf("%d%d", &N, &M) ;
        memset(K, 0, sizeof(K)) ;
        for(i = 1; i <= M; i ++){
            scanf("%d%d", &a, &b) ;
            K[a][a] ++ ;
            K[b][b] ++ ;
            K[a][b] -- ;
            K[b][a] -- ; 
        } 
        printf("%d\n", gauss_work()) ;
     }
}

然后高消的,在spoj上過了上一份代碼在SPOJ上WA了…但沒有大問題,應該是編譯器的鍋

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>


using namespace std ;
const int MAXN = 3010 ;
const double eps = 1e-12 ;
int N, T, M, a, b, i, j, k, t ; double K[MAXN][MAXN] ;

int _back(int x) {if(x <= eps || x >= -eps ) return 0 ;else return x < 0 ? -1 : 1 ;}
void Gauss_work(){
    N -- ; int big ; double t, ans = 1 ;
    for(i = 1; i <= N; i ++){
        big = i ;
        for(j = i + 1 ; j <= N; j ++)
            if(_back(K[big][i] - K[j][i]) < 0 ) big = j ;
        if(big != i) swap(K[i], K[big]) ; if(!K[i][i]) {printf("0\n") ; return ;}
        for(j = i + 1; j <= N; j ++){
            t = K[j][i] / K[i][i] ;
            for(k = i; k <= N + 1; k ++)
                K[j][k] -= t * K[i][k] ;
        }
    }
    for(i = 1; i <= N; i ++) ans = ans * K[i][i];
    printf("%.0f\n",abs(ans)) ;
}
int main(){
     cin >> T ;
     while(T --){
        scanf("%d%d", &N, &M) ;
        memset(K, 0, sizeof(K)) ;
        for(i = 1; i <= M; i ++){
            scanf("%d%d", &a, &b) ;
            K[a][a] ++ ;
            K[b][b] ++ ;
            K[a][b] -- ;
            K[b][a] -- ; 
        } 
        Gauss_work() ;
     }
     return 0 ;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM