[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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章