qoj #17733. Tromino 铺设 题解
这种确定一个中心点必须要放一个 \(L\) 型联通块,非常重要的一个转化就是,把相邻的四个点分成相对的两组每组必须选一个,这是等价的。然后发现变成了什么,对于每个中心点把每组的两个点连一条边,最后建成图,于是最后转化为“为每个边都匹配一个端点,有些端点被ban了,每个端点只能用一次的方案数”。只需要对每个联通块的方案数累乘即可。
1.树,ban掉 \(>=2\) 个点的答案是 \(0\) ,ban掉 \(1\) 个点的答案是 \(1\) ,ban掉 \(0\) 个点的答案是 \(siz\) 。
2.点数 \(=\) 边数,ban掉 \(>=1\) 个点的答案是 \(0\) ,ban掉 \(0\) 个点的答案是 \(2\) 。
3.点数 \(<\) 边数,答案就是 \(0\) 。
至于最后咋维护, \(DFS\) 或者并查集啥的简单维护一下就行,复杂度 \(O(n)\) 。
代码:
#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
#define ll long long
using namespace std;
const int N = 1010;
const int Mod = 1e9+7;
int n, m;
char g[N][N];
int pre[N * N], cnt[N * N], cnt_e[N * N], cnt_p[N * N];
inline int turn(int x, int y) {
y++;
return x * (m + 2) + y;
}
inline void init() {
for (int i = 0; i <= n + 1; i++) {
for (int j = 0; j <= m + 1; j++) {
int num = turn(i, j);
pre[num] = num;
cnt[num] = 1;
cnt_e[num] = 0;
cnt_p[num] = (g[i][j] != '.');
}
}
}
inline int find(int x) {
if (x == pre[x])
return x;
return pre[x] = find(pre[x]);
}
inline void merge(int x, int y) {
x = find(x);
y = find(y);
if (x == y) {
cnt_e[x]++;
return ;
}
if (cnt[x] > cnt[y])
swap(x, y);
pre[x] = y;
cnt[y] += cnt[x];
cnt_p[y] += cnt_p[x];
cnt_e[y] += cnt_e[x] + 1;
}
inline void solve() {
cin >> n >> m;
for (int i = 0; i <= n + 1; i++) {
for (int j = 0; j <= m + 1; j++) {
if (i < 1 || i > n || j < 1 || j > m)
g[i][j] = '#';
else
cin >> g[i][j];
}
}
init();
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (g[i][j] == 'o') {
merge(turn(i + 1, j), turn(i - 1, j));
merge(turn(i, j + 1), turn(i, j - 1));
}
}
}
ll ans = 1;
for (int i = 1; i <= (n + 2) * (m + 2); i++) {
if (pre[i] != i || cnt[i] == 1)
continue;
if (cnt[i] < cnt_e[i])
ans = 0;
else if (cnt[i] == cnt_e[i]) {
if (cnt_p[i])
ans = 0;
else
ans = ans * 2 % Mod;
} else {
if (cnt_p[i] == 0)
ans = ans * cnt[i] % Mod;
else if (cnt_p[i] > 1)
ans = 0;
}
}
cout << ans << '\n';
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int T;
cin >> T;
while (T--)
solve();
return 0;
}```

浙公网安备 33010602011771号