洛谷 P4455 [CQOI2018]社交网络(矩阵树定理)

题目描述

传送门

大意:给出n个点的有向图,求以1为根的树形图个数。(1≤n≤250)

思路

有向图的基尔霍夫定理。

类比无向图生成树的计算方式,先根据原图构出基佬♂霍夫矩阵。呸!是基尔霍夫矩阵。无向图中为度数矩阵-邻接矩阵,有向图为入度矩阵-邻接矩阵。(不证)

这题直接构建出矩阵,然后求出其余子式(即去掉某一行一列后的行列式,要求行列相同)。求行列式的基本想法就是通过初等行变换使之化成上三角,然后行列式就是主对角线的乘积。

初等行变换就用高消去搞,交换两行行列式取反,将某行乘K加到另一行行列式不变。取模就直接将除法改成逆元就行了。

顺便有一个性质:
拉普拉斯矩阵是半正定矩阵,半正定矩阵的行列式是非负的。——不是我说的
余子式Mij当i+j为偶数时非负。(如果是负数还怎么求其生成树个数啊)

于是在不用取模的情况下取绝对值就行了,可以不计交换次数。

注意这题要求根为1,所以只能求M(1,1)。

代码

#include 
#define maxn 255
#define M 10007

using namespace std;

int n, m, A[maxn][maxn];

int Pow(int x, int y){
    int res = 1;
    while(y){
        if(y & 1)  res = res * x % M;
        x = x * x % M;
        y >>= 1;
    }
    return res;
}

int Gauss(){
    int ans = 1, cnt = 0;
    for(int i = 2; i <= n; i++){
        int x = i;
        for(; x <= n && !A[x][i]; x++);

        if(x != i)  cnt ^= 1, swap(A[x], A[i]);

        if(!A[i][i])  return 0;
        ans = ans * A[i][i] % M;

        int Inv = Pow(A[i][i], M-2);
        for(int j = i+1; j <= n; j++){
            if(!A[j][i])  continue;
            int Mul = A[j][i] * Inv % M;
            for(int k = 2; k <= n; k++)
                A[j][k] = (A[j][k] - A[i][k] * Mul % M + M) % M;
        }
    }
    if(cnt)  ans = (M - ans) % M;
    return ans;
}

int main(){

    scanf("%d", &n);
    scanf("%d", &m);

    int a, b;
    for(int i = 1; i <= m; i++){
        scanf("%d%d", &a, &b);
        A[b][a] --;
        A[a][a] ++;
    }

    for(int i = 1; i <= n; i++)
        for(int j = 1; j <= n; j++)
            A[i][j] = (A[i][j] + M) % M;

    printf("%d\n", Gauss());

    return 0;
}

你可能感兴趣的:(高斯消元,矩阵树定理)