2025.03.04 CW 模拟赛 D. 积木
D. 积木
和之前容斥专题的一道题有点像.
思路
注意到虽然 \(1 \le n, m \le 50\), 但是 *
的个数不超过 12 个. 于是我们可以考虑对 *
的个数进行状压, 也就是钦定哪些 *
必须作为积木的中心 \((\)下文统称为 o
\()\).
钦定完成, 我们考虑什么情况下是不合法的. 如下图, 这两种情况一定不合法, 因为中间的 o
不能被填入积木.
形式化一点的说, 我们对于每一个 o
判断它的四联通块, 我们记其周围不合法的块数为 \(cnt\), 这里的「不合法」指的是超出边界 / 有 o
. 如果 \(cnt > 1\), 那么这种情况一定就不合法; 如果 \(cnt = 1\) 那么我们称这个 o
被「定向」了.
这里的「定向」是什么意思呢? 指的是积木只能朝某个方向摆放, 不能随意改变方向. 如下图所示.
这是简单的三种情况, 可以发现他们有一个共同特点: 都将其左边的一个格子占了 \((\)使得它被「定向」的 o
不可能在它左边, 否则三个连在一起就不合法了\()\). 所以若此时左边的 o
再被「定向」, 那么这种情况也不合法了. 当然还有一种情况, 使得两个 o
一起被「定向」了. 如图, 可以发现无论怎么摆放, 这两个中心所占领的格子永远是黄线框起来的部分. 这种定向有什么不同呢? 由于有 2 种摆放方式, 它会使得最终答案 \(\times 2\).
这样, 我们可以将所有有关系的 o
丢在一个并查集里维护, 例如上 2 张图片中的情况, 两个 o
中间隔了一个 / 在对角上. 注意在合并时, 如果发现两个块均被定向了, 那么也是不合法的.
我们对于每一个块可以记两个变量 \(t_1, t_2\), 分别表示这个块是否被定向 \((\)包含对角的情况\()\) 和是否被定向 \((\)不包含对角的情况\()\). 如果在合并两个块时两个块的 \(t_1\) 都是 1, 那么直接 \(\rm{return}\) 就好了. 对于对角的情况, 做法比较聪明, 我们合并两次, 第一次合并是平凡的, 第二次合并我们判断是否有解. 如果实在没看懂可以看看代码.
void merge(int x, int y) {
x = find(x), y = find(y);
if (x ^ y) {
if (t1[x] and t1[y]) return wrong = 1, void();
fa[x] = y;
siz[y] += siz[x];
t1[y] or_eq t1[x], t2[y] or_eq t2[x];
}
else {
if (t1[x]) return wrong = 1, void();
t1[y] = 1;
}
}
最后就是统计答案了. 显然, 我们肯定会对于每一个并查集单独统计最后将答案乘起来.
考虑对于一个并查集统计答案. 我们记 \(sz\) 表示并查集的大小, 如果 \(t_1, t_2\) 都是 0, 那么 \(ans = (3 \times sz + 1) \times ans\) \((\)这是因为我们每一个并查集里的如果一个积木被定向了, 那么其他积木都会被定向, 而每个积木有 3 种被定向的方式, 最后 \(+1\) 是因为有且仅有 1 种方式使得所有积木不被定向\()\); 若 \(t_1\) 是 1, \(t_2\) 是 0, 那么将 \(ans\) 乘 2 即可.
完整代码
#include "iostream"
#include "numeric"
#include "algorithm"
#include "cstring"
#include "vector"
using namespace std;
#define sz(x) ((int)x.size())
typedef pair<int, int> pii;
constexpr int N = 51, mod = 1e9 + 7;
constexpr int dx[] = {0, 0, 1, -1}, dy[] = {1, -1, 0, 0};
int n, m, cnt, ans = 0, fa[N * N];
int vis[N][N], t1[N * N], t2[N * N], siz[N * N], hsh[N][N], wrong;
char mp[N][N];
vector<pii> ps, po, fn;
void addon(int &x, int y) { if ((x += y) >= mod) x -= mod; }
int check(int x, int y) {
if (x < 1 or x > n or y < 1 or y > m) return 0;
return 1;
}
int find(int x) { return fa[x] == x ? x : fa[x] = find(fa[x]); }
void merge(int x, int y) {
x = find(x), y = find(y);
if (x ^ y) {
if (t1[x] and t1[y]) return wrong = 1, void();
fa[x] = y;
siz[y] += siz[x];
t1[y] or_eq t1[x], t2[y] or_eq t2[x];
}
else {
if (t1[x]) return wrong = 1, void();
t1[y] = 1;
}
}
int deal() {
int res = 1;
wrong = 0;
fill(t1, t1 + sz(fn) + 1, 0);
fill(t2, t2 + sz(fn) + 1, 0);
fill(siz, siz + sz(fn) + 1, 1);
iota(fa, fa + sz(fn) + 1, 0);
for (auto [x, y] : fn) {
int tot = 0;
for (int i = 0, xx, yy; i < 4; ++i) {
xx = x + dx[i], yy = y + dy[i];
tot += !check(xx, yy) or vis[xx][yy];
}
if (tot > 1) return 0;
t1[hsh[x][y]] = t2[hsh[x][y]] = tot == 1;
if (check(x - 2, y) and vis[x - 2][y]) merge(hsh[x - 2][y], hsh[x][y]);
if (check(x, y - 2) and vis[x][y - 2]) merge(hsh[x][y - 2], hsh[x][y]);
if (check(x - 1, y - 1) and vis[x - 1][y - 1]) merge(hsh[x - 1][y - 1], hsh[x][y]), merge(hsh[x - 1][y - 1], hsh[x][y]);
if (check(x - 1, y + 1) and vis[x - 1][y + 1]) merge(hsh[x - 1][y + 1], hsh[x][y]), merge(hsh[x - 1][y + 1], hsh[x][y]);
if (wrong) return 0;
}
for (auto [x, y] : fn) if (find(hsh[x][y]) == hsh[x][y] and !t2[hsh[x][y]]) {
if (!t1[hsh[x][y]]) res = res * (3 * siz[hsh[x][y]] + 1ll) % mod;
else addon(res, res);
}
return res;
}
void init() {
scanf("%d %d", &n, &m);
for (int i = 1; i <= n; ++i) {
scanf("%s", mp[i] + 1);
for (int j = 1; j <= m; ++j) {
if (mp[i][j] == 'o') po.emplace_back(i, j), vis[i][j] = 1;
else if (mp[i][j] == '*') ps.emplace_back(i, j);
}
}
cnt = sz(ps);
}
void calculate() {
for (int S = 0, c = 0; S < (1 << cnt); c = 0, ++S) {
fn = po, c = 0;
memset(hsh, 0, sizeof hsh);
memset(vis, 0, sizeof vis);
for (int i = 0; i < cnt; ++i)
if (S >> i & 1) fn.push_back(ps[i]);
for (auto [x, y] : fn) hsh[x][y] = ++c, vis[x][y] = 1;
addon(ans, deal());
}
printf("%d", ans);
}
void solve() {
init();
calculate();
}
int main() {
solve();
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现