此题P6008 [USACO20JAN] Cave Paintings P [计数][并查集]

传送门
我们发现,若从上往下遍历矩阵会涉及到一些类似删除的操作,这是难以维护的
所以可以从下往上,也就是从高度低往高度高遍历,这样变成添加操作维护起来是便捷的
根据乘法原理,最终答案为所有连通块方案数的乘积。考虑一个连通块的方案数怎么计算,若一个连通块只占据某一行,那么方案数为\(2\),即选或不选。
若连通块占据超过两行,就需要递推统计。
例如对于

.
. .

这样的连通块方案数为3
而对于

. . . . . . .
. . # # # . #
# # # # # . .

这样的连通块,首先不看第一行,计算后两行的两个连通块方案数分别为\(2\)\(3\),则通过第一行将他们连在一起之后的方案数应该为\(7\),因为可以第一行不取,此时对于分开的两个小连通块应该是乘法原理,若第一行取,则整个连通块都会充满水
这可以用并查集维护

#include<bits/stdc++.h>
#define int long long

using namespace std;

const int N = 2e6 + 10;
const int inf = 1e18 + 10;
const int mod = 1e9 + 7;
int n,m,fa[N],rk[N],s[N];
char mp[1010][1010];
int pos[1010][1010],cnt;

int find(int x) {
    return fa[x] == x ? fa[x] : fa[x] = find(fa[x]);
}

void unionn(int x,int y) {
    x = find(x);
    y = find(y);
    fa[x] = y;
}

int quickMul(int x,int k) {
    int res = 1;
    while(k) {
        if(k & 1) res = res * x % mod;
        x = x * x % mod;
        k >>= 1;
    }
    return res;
}

void solve() {
    cin >> n >> m;
    for(int i = 1;i <= n;i++)
        for(int j = 1;j <= m;j++) {
            cin >> mp[i][j];
            if(mp[i][j] == '.') {
                pos[i][j] = ++cnt;
                fa[cnt] = cnt;
                rk[cnt] = 1;
                s[cnt] = 1;
            }
        }
    int ans = 1,num = 0;
    unordered_map<int,bool> vis;
    for(int i = n;i >= 1;i--) {
        vis.clear();
        for(int j = 1;j <= m;j++) {
            if(mp[i][j] != '.') continue;
            if(mp[i][j - 1] == '.') unionn(pos[i][j],pos[i][j - 1]); //首先将同一行联通的加入并查集
        }
        for(int j = 1;j <= m;j++) {
            if(mp[i][j] != '.') continue;
            if(mp[i + 1][j] == '.' && find(pos[i][j]) != find(pos[i + 1][j])) { //然后看当前行与下一行联通
                s[find(pos[i][j])] *= rk[find(pos[i + 1][j])] * s[find(pos[i + 1][j])] % mod; //s记录所有下一行连通块方案数乘积,由于在当前行当中,可能两个空白段是通过下一行联系起来的,所以是rk*s
                s[find(pos[i][j])] %= mod;
                unionn(pos[i + 1][j],pos[i][j]);
            }
        }
        for(int j = 1;j <= m;j++)
            if(mp[i][j] == '.' && !vis[find(pos[i][j])]) {
                rk[find(pos[i][j])] += s[find(pos[i][j])]; //rk记录当前连通块的方案数
                rk[find(pos[i][j])] %= mod;
                s[find(pos[i][j])] = 1;
                vis[find(pos[i][j])] = 1;
            }
    }
    vis.clear();
    for(int i = 1;i <= n;i++)
        for(int j = 1;j <= m;j++) {
            if(mp[i][j] == '.' && !vis[find(pos[i][j])])
                ans = ans * rk[find(pos[i][j])] % mod;
            vis[find(pos[i][j])] = 1;
        }
    cout << ans;
}

signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    int t = 1;
    while(t--) solve();
    
    return 0;
}
posted @ 2025-08-06 16:55  孤枕  阅读(7)  评论(0)    收藏  举报