BZOJ4031 [HEOI2015] 小Z的房间

@(BZOJ)[行列式, Matrix-Tree定理, 高斯消元]

Description

你突然有了一个大房子,房子里面有一些房间。事实上,你的房子可以看做是一个包含\(n * m\)个格子的格状矩形,每个格子是一个房间或者是一个柱子。在一开始的时候,相邻的格子之间都有墙隔着。

你想要打通一些相邻房间的墙,使得所有房间能够互相到达。在此过程中,你不能把房子给打穿,或者打通柱子(以及柱子旁边的墙)。同时,你不希望在房子中有小偷的时候会很难抓,所以你希望任意两个房间之间都只有一条通路。现在,你希望统计一共有多少种可行的方案。

Input

第一行两个数分别表示\(n\)\(m\)
接下来n行,每行m个字符,每个字符都会是’.’或者’’,其中’.’代表房间,’’代表柱子。

Output

一行一个整数,表示合法的方案数 \(Mod 10^9\)

Sample Input

3 3
...
...
.*.

Sample Output

15

HINT

对于前\(100%\)的数据,\(n,m \le 9\)

Solution

需要運用到Matrix-Tree定理和一些行列式的知識(見前面的).

下面我们介绍一种新的方法——Matrix-Tree定理(Kirchhoff矩阵-树定理)。Matrix-Tree定理是解决生成树计数问题最有力的武器之一。它首先于1847年被Kirchhoff证明。在介绍定理之前,我们首先明确几个概念:

  1. \(G\)的度数矩阵\(D[G]\)是一个\(n * n\)的矩阵,并且满足:当\(i \ne j\)时,\(d_{i, j}\)=0;当\(i = j\)时,\(d_{i, j}\)等于\(v_i\)的度数。
  2. \(G\)的邻接矩阵\(A[G]\)也是一个\(n * n\)的矩阵, 并且满足:如果\(v_i\)\(v_j\)之间有边直接相连,则\(a_{i, j}=1\),否则为\(0\)

我们定义\(G\)的Kirchhoff矩阵(也称为拉普拉斯算子)\(C[G]\)\(C[G] = D[G] - A[G]\),则Matrix-Tree定理可以描述为:\(G\)的所有不同的生成树的个数等于其Kirchhoff矩阵\(C[G]\)任何一个\(n - 1\)阶主子式的行列式的绝对值。所谓\(n - 1\)阶主子式,就是对于\(r (1 \le r \le n)\),将\(C[G]\)的第\(r\)行、第\(r\)列同时去掉后得到的新矩阵,用\(Cr[G]\)表示。

實際上, 這個矩陣並不需要由兩個相減得來(畢竟Kirchoff矩陣一般只會考它的結論). 可以直接枚舉兩個點\(u\)\(v\), 假如\(u \to v\)有一條連邊, 則矩陣中\(det[u][u] ++\)同時\(det[u][v] --\). 這樣得到的結果相當於這兩個矩陣相減. 然後再這個行列式上跑類似於高斯消元的東西就可以了.

注意: 一定要把任意的第\(r\)行第\(r\)列去掉后再求出行列式的值, 否则求出的答案永远都是\(0\)

還有一點要注意的:

然后关于取模的高斯消元,有两种实现方法:
①求乘法逆元
②欧几里得算法,同时把其他项也给消掉。注意在交换的时候,根据行列式的性质,取负号。注意全部变成正数再进行gcd。

差不多就是這些了.

#include<cstdio>
#include<cctype>
#include<cstring>
#include<algorithm>
using namespace std;

inline void read(int &x)
{
    x = 0;
    int flag = 1;
    char c;
    while(! isdigit(c = getchar()))
        if(c == '-')
            flag *= - 1;
    while(isdigit(c))
        x = x * 10 + c - '0', c = getchar();
    x *= flag;
}

inline void read(char &c)
{
    while(! isgraph(c = getchar()));
}

void println(int x)
{
    if(x < 0)
        putchar('-'), x *= - 1;
    if(x == 0)
        putchar('0');
    int ans[1 << 5], top = 0;
    while(x)
        ans[top ++] = x % 10, x /= 10;
    for(; top; top --)
        putchar(ans[top - 1] + '0');
    putchar('\n');
}

const int N = 1 << 4, M = 1 << 4;
const long long MOD = (long long)1e9;

const int movesLikeJagger[4][2] = {{1, 0}, {- 1, 0}, {0, 1}, {0, - 1}};

int n, m;

char s[N][M];
int id[N][M];

long long det[N * M][N * M];

inline int legal(int x, int y)
{
    return x && x <= n && y && y <= m && s[x][y] == '.';
}

inline void add(int u, int v)
{
    det[u][u] ++, det[u][v] --;
}

long long calculate(int n)
{
    for(int i = 0; i < n; i ++)
        for(int j = 0; j < n; j ++)
            det[i][j] = (det[i][j] + MOD) % MOD;
            
    long long sign = 0;
    
    for(int i = 0; i < n; i ++)
    {
        int j;
        
        for(j = i; j < n; j ++)
            if(det[j][i])
                break;
        
        if(j == n)
            continue;
        
        if(i != j)
        {
            sign ^= (i != j);
            for(int k = i; k < n; k ++)
                swap(det[j][k], det[i][k]);
        }
        
        for(int j = i + 1; j < n; j ++)
            while(det[j][i])
            {
                int tmp = det[j][i] / det[i][i];
                
                for(int k = i; k < n; k ++)
                    det[j][k] = (det[j][k] - det[i][k] * tmp % MOD + MOD) % MOD;
                    
                if(! det[j][i])
                    break;
                    
                sign ^= 1;
                for(int k = i; k < n; k ++)
                    swap(det[i][k], det[j][k]);
            }
    }
    
    long long ret = 1;
    
    for(int i = 0; i < n; i ++)
        ret = (ret * det[i][i]) % MOD;
        
    if(sign)
        ret = (MOD - ret) % MOD;
        
    return ret;
}

int main()
{
    #ifndef ONLINE_JUDGE
    freopen("BZOJ4031.in", "r", stdin);
    freopen("BZOJ4031.out", "w", stdout);
    #endif
    
    read(n), read(m);
    
    for(int i = 1; i <= n; i ++)
        for(int j = 1; j <= n; j ++)
            read(s[i][j]);
            
    int cnt = 0;
            
    for(int i = 1; i <= n ; i ++)
        for(int j = 1; j <= m; j ++)
            if(s[i][j] == '.')
                id[i][j] = cnt ++;
                
    memset(det, 0, sizeof(det));
            
    for(int x = 1; x <= n; x ++)
        for(int y = 1; y <= m; y ++)
            if(legal(x, y))
                for(int i = 0; i < 4; i ++)
                {
                    int nextX = x + movesLikeJagger[i][0], nextY = y + movesLikeJagger[i][1];
                    if(legal(nextX, nextY))
                        add(id[x][y], id[nextX][nextY]);
                }
    
    int ans = calculate(cnt - 1);
    
    println(ans);
}

转载于:https://www.cnblogs.com/ZeonfaiHo/p/6421816.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值