生成树个数(基尔霍夫矩阵)

Problem 2. tcount
Input file: tcount.in
Output file: tcount.out
Time limit: 1 second
Mr.H发现了一个无向连通图,它觉得,如果选出一些边来,使得这个图变为一棵树,那么这个边集就非常棒。
现在,Mr. H 想让你帮忙求出有多少个非常棒的边集?
Input
第1行,包含2个整数:n m,表示有n个点m条边。
接下来m行,每行2个整数:u v,表示一条边。
Output
输出1行,包含1个整数,表示方案数模10^9 + 7。
Sample
tcount.in
3 3
1 2
2 3
1 3
tcount.out
3
tcount.in
4 6
1 2
1 3
1 4
2 3
2 4
3 4
tcount.out
16
Note
对于30% 的数据,1 <= n;m <= 12;
对于100% 的数据,1 <= n <= 100, 1 <= m <= n(n-1)/2
数据保证没有重边。

思路:
度数矩阵 a[i][i]为点i的度 其他位置为0
邻接矩阵 a[i][i]=0; a[i][j]=[i与j相连]
基尔霍夫矩阵 = 度数矩阵 - 邻接矩阵
Matrix-Tree 定理:
一个n个点m条边的无向图的生成树总数为其对应的基尔霍夫矩阵的n-1阶余子式(行列式)。

行列式是什么?
一种定义是:
det(A) = Σ (-1)^(i1i2..in) * a1i1 * a2i2 anin
i1i2..in

其中i1i2…in 是一个1到n的排列,^(i1i2..in) 表示该排列的逆序对个数
行列式的性质
1.某行乘一个数,那么行列式也乘那个数。
2.某行加到另一行,行列式不变
3.交换两行,行列式取反相反数(交换两个数逆序对个数总是改变奇数个)
生成树个数(基尔霍夫矩阵)_第1张图片
行列式的计算—高斯消元!
消元的过程中行列式不变,交换一次行列式*-1。所以消元完成后,矩阵只剩下对角线上的主元。
根据行列式的要求,我们要在矩阵中每一行找出且只能找出一个数 每一列找出且只能找出一个数。
那么就是对角线上的数了(只有一种排列),把对角线上的数乘起来就是行列式了。

由于中途有mod,double的除法又有可能炸精度。
所以有两种解决方法,一个是辗转相除,一个是逆元,下面的代码两种都给出来了。

#include   
#include   
#include   
#define LL long long
using namespace std;  

const int MOD = 1e9+7;
LL a[105][105];
int n, m, sign, du[105];

void solve(int N){
    sign = 0; LL ans = 1;
    for(int i=1; ifor(int j=i+1; j//当前之后的每一行第一个数要转化成0
            int x=i, y=j;
            while( a[y][i] ){//利用gcd的方法,不停地进行辗转相除
                LL t = a[x][i] / a[y][i];
                for(int k=i; kif(x != i){//奇数次交换,则D=-D'整行交换
                for(int k=1; k1;//行列式*-1
            }
        }
        if( !a[i][i] ){//斜对角中有一个0,则结果为0
            printf("0\n");
            return ;
        }
        else ans = ans * a[i][i] % MOD;
    }
    if(sign != 0) ans *= -1;
    if(ans < 0) ans += MOD;//
    printf("%I64d\n", ans);
    return ;
}

int main(){
    freopen ("tcount.in", "r", stdin);
    freopen ("tcount.out", "w", stdout);
    scanf("%d%d", &n, &m);
    for(int i=1; i<=m; i++){
        int x, y; scanf("%d%d", &x, &y);
        a[x][y] = a[y][x] = -1;
        du[x]++; du[y]++;
    }
    for(int i=1; i<=n; i++) a[i][i] = du[i];
    solve(n);
    return 0;
}
#include 
using namespace std;

const int N = 110;
const int Mod = 1e9 + 7;

int n, m;
int aa[N][N];

int mpow(int a, int b) {
    int rt;
    for(rt = 1; b; b>>=1,a=(1LL*a*a)%Mod)
        if(b & 1) rt=(1LL*rt*a)%Mod;
    return rt;
}

int inverse(int a) {
    return mpow(a, Mod-2);
}

int det(int n) {
    int rt = 1;
    for(int i=1; i<=n; i++) {
        int j = -1, k;
        for(k=i; k<=n; k++)
            if(aa[k][i] != 0) {
                j = k;
                break;
            }
        if(j == -1) return 0;
        if(i != j) {
            for(k=i; k<=n; k++) {
                swap(aa[i][k], aa[j][k]);
                rt = (Mod - rt) % Mod;
            }
        }
        int inv = inverse(aa[i][i]);
        for(j = i + 1; j <= n; j++) {
            if(aa[j][i] == 0) continue;
            int r = 1LL * aa[j][i] * inv % Mod;
            for(k = i; k <= n; k++) {
                aa[j][k] -= 1LL * aa[i][k] * r % Mod;
                if(aa[j][k] < 0) aa[j][k] += Mod;
            }
        }
    }
    for(int i = 1; i <= n; i++)
        rt = 1LL * rt * aa[i][i] % Mod;
    return rt;
}

int main() {
    scanf("%d%d", &n, &m);
    for(int i=1; i<=m; i++) {
        int u, v;
        scanf("%d%d", &u, &v);
        aa[u][u]++;
        aa[v][v]++;
        aa[u][v] = aa[v][u] = Mod-1;
    }
    printf("%d\n", det(n-1));
}

你可能感兴趣的:(—————数论—————,—————模板—————,基尔霍夫矩阵,—————练习赛—————,数论,基尔霍夫矩阵)