[Code+ #2] 白金元首與獨舞(建圖 + 矩陣樹定理推廣) | 錯題本

文章目錄

題目

[Code+ #2] 白金元首與獨舞

分析

首先不難想到直接用 nmnm 個點上下左右連邊建圖,既然要走出去,就把“外面”建成一個點 rr,所以問題變爲求以 rr 爲根的內向樹個數。但這樣時間複雜度是 O((nm)3)O((nm)^3)。觀察到 k300k \leq 300,因此考慮只保留 .,把 . 向它上下左右能走到的另一個 . 連邊即可。記憶化搜索實現,注意每個點都要搜一遍找環。


如果是求有向圖以 uu 爲根的個數,我們只需要算 LGu,u|{L_G}_{u, u}| 就是答案。
證明:在 矩陣樹定理 - 連通性引理 3 的證明中提到,高斯消元的最後一個元素 ln,n=0l'_{n, n} = 0,此時他的入度爲 0,這就意味着它是根,因此把 LLuuuu 列刪掉就可以看出這個圖是不是以 uu 爲根的樹。若是,則 Lu,u=1|L_{u, u}| = 1,否則 Lu,u=0|L_{u, u}| = 0

代碼

#include <bits/stdc++.h>

typedef std::pair<int, int> PII;

const int MAXN = 200;
const int MAXK = 300;
const int MOD = 1000000007;

int N, M;
char Map[MAXN + 5][MAXN + 5];

inline int Add(int x, const int &y) {
    x += y; if (x >= MOD) x -= MOD; return x;
}

inline int Sub(int x, const int &y) {
    x -= y; if (x < 0) x += MOD; return x;
}

inline int Mul(const int &x, const int &y) {
    return (long long)x * y % MOD;
}

int Pow(int x, int y) {
    int ret = 1;
    while (y) {
        if (y & 1)
            ret = Mul(ret, x);
        x = Mul(x, x);
        y >>= 1;
    }
    return ret;
}

int Mat[MAXK + 5][MAXK + 5];

int Det(int n) {
    n--;
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= n; j++)
            Mat[i][j] = Sub(Mat[i][j], 0);
    int ret = 1;
    for (int i = 1; i <= n; i++) {
        int p = i;
        for (int j = i + 1; j <= n; j++)
            if (Mat[p][i] < Mat[j][i])
                p = j;
        if (p != i) {
            std::swap(Mat[p], Mat[i]);
            ret = Sub(0, ret);
        }
        if (!Mat[i][i])
            return 0;
        ret = Mul(ret, Mat[i][i]);
        int inv = Pow(Mat[i][i], MOD - 2);
        for (int j = i + 1; j <= n; j++) {
            int tmp = Mul(Mat[j][i], inv);
            for (int k = i; k <= n; k++)
                Mat[j][k] = Sub(Mat[j][k], Mul(Mat[i][k], tmp));
        }
    }
    return ret;
}

int DirID[30];
const int Dir[4][2] = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};

int ID[MAXN + 5][MAXN + 5];
int To[MAXN + 5][MAXN + 5], Vis[MAXN + 5][MAXN + 5];

int Go(int x, int y, int c) {
    if (To[x][y])
        return To[x][y];
    if (Vis[x][y] == c)
        return To[x][y] = -1;
    Vis[x][y] = c;
    int id = DirID[Map[x][y] - 'a'];
    return To[x][y] = Go(x + Dir[id][0], y + Dir[id][1], c);
}

void AddEdge(int u, int v) {
    Mat[v][v]++, Mat[u][v]--;
}

int main() {
    DirID['R' - 'a'] = 0, DirID['L' - 'a'] = 1;
    DirID['D' - 'a'] = 2, DirID['U' - 'a'] = 3;
    int T; scanf("%d", &T);
    while (T--) {
        scanf("%d%d", &N, &M);
        for (int i = 1; i <= N; i++)
            scanf("%s", Map[i] + 1);
        int cnt = 0;
        for (int i = 1; i <= N; i++)
            for (int j = 1; j <= M; j++) {
                if (Map[i][j] == '.')
                    To[i][j] = ID[i][j] = ++cnt;
                Vis[i][j] = 0;
            }
        for (int i = 0; i <= N + 1; i++)
            for (int j = 0; j <= M + 1; j++) {
                if (i == 0 || i == N + 1 || j == 0 || j == M + 1)
                    To[i][j] = cnt + 1;
                else if (Map[i][j] != '.')
                    To[i][j] = 0;
            }
        for (int i = 1; i <= cnt + 1; i++)
            for (int j = 1; j <= cnt + 1; j++)
                Mat[i][j] = 0;
        int col = 0;
        bool C = false;
        for (int i = 1; i <= N && !C; i++)
            for (int j = 1; j <= M && !C; j++) {
                if (Map[i][j] == '.') {
                    for (int k = 0; k < 4; k++) {
                        int x = i + Dir[k][0], y = j + Dir[k][1];
                        int u = Go(x, y, ++col);
                        if (u == -1)
                            C = true;
                        else AddEdge(u, ID[i][j]);
                    }
                }
                else if (Go(i, j, ++col) == -1)
                    C = true;
            }
        if (C) {
            puts("0");
            continue;
        }
        printf("%d\n", Det(cnt + 1));
    }
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章