2025.03.04 CW 模拟赛 D. 积木

D. 积木

和之前容斥专题的一道题有点像.

思路

注意到虽然 \(1 \le n, m \le 50\), 但是 * 的个数不超过 12 个. 于是我们可以考虑对 * 的个数进行状压, 也就是钦定哪些 * 必须作为积木的中心 \((\)下文统称为 o\()\).

钦定完成, 我们考虑什么情况下是不合法的. 如下图, 这两种情况一定不合法, 因为中间的 o 不能被填入积木.

illegal.png

形式化一点的说, 我们对于每一个 o 判断它的四联通块, 我们记其周围不合法的块数为 \(cnt\), 这里的「不合法」指的是超出边界 / 有 o. 如果 \(cnt > 1\), 那么这种情况一定就不合法; 如果 \(cnt = 1\) 那么我们称这个 o 被「定向」了.

这里的「定向」是什么意思呢? 指的是积木只能朝某个方向摆放, 不能随意改变方向. 如下图所示.

 _1_.png

这是简单的三种情况, 可以发现他们有一个共同特点: 都将其左边的一个格子占了 \((\)使得它被「定向」的 o 不可能在它左边, 否则三个连在一起就不合法了\()\). 所以若此时左边的 o 再被「定向」, 那么这种情况也不合法了. 当然还有一种情况, 使得两个 o 一起被「定向」了. 如图, 可以发现无论怎么摆放, 这两个中心所占领的格子永远是黄线框起来的部分. 这种定向有什么不同呢? 由于有 2 种摆放方式, 它会使得最终答案 \(\times 2\).

.png

这样, 我们可以将所有有关系的 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;
}
posted @   Steven1013  阅读(1)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示