CF 1368G Shifting Dominoes

题意

有一个 n × m n \times m n×m 的网格上放满了 1 × 2 1 \times 2 1×2 的多米诺骨牌,你可以拿走某个多米诺骨牌,然后让剩下某个多米诺骨牌沿着它的长边移动一格(必须要有空位相邻才能移动),这样会让两个空格的位置产生变化。问最终可以产生多少种不同的两个空格位置。

题解

写了个做法死活过不了样例 3,看到有人写 SegBeats 还以为自己假了,检查了半天,结果是扫描线写挂了……

首先冷静分析一下,把空格看成是独立的点,每次移动多米诺骨牌就相当于把空格沿着多米诺骨牌的长边移动两格。

于是按照上述移动方式连有向边,比如一个 ( a , b ) , ( a , b + 1 ) (a,b),(a,b+1) (a,b),(a,b+1) 的多米诺骨牌,就连边 ( a , b − 1 ) → ( a , b + 1 ) (a,b-1) \rightarrow (a,b+1) (a,b1)(a,b+1) ( a , b + 2 ) → ( a , b ) (a,b+2) \rightarrow (a,b) (a,b+2)(a,b),然后来分析一下这个图的性质。

  1. 该图无环。如果产生环,那么这个环包住的内部空间含有奇数个方格(可以画一画验证一下),与题意不符(不能被多米诺骨牌完全覆盖)。
  2. 该图是一个有向树森林。连边方式决定了一个点有且仅有一条入边。
  3. 对于网格图上相邻的两个格子,他们对应的点在图中不在一个连通块。将原图黑白染色,显然一个连通块中所有格子颜色相同。

但是移动之后会不会出现某个多米诺骨牌连续向右移动两格的情况?如果这样的话那么上面的图是没法处理的。但是这种情况不可能发生,不然初始状态下直接删除那个移动两次的多米诺骨牌即可。

于是问题转化为:一个有根树森林,每次把 u u u 的子树和 v v v 的子树中的点两两配对扔到一个 set 里,问最后 set 的 size。求出该森林的 dfs 序,就变成了每次加入一个矩形,求所有矩形的面积并。扫描线即可,复杂度 O ( n log ⁡ n ) O(n \log n) O(nlogn)

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
template<typename T> inline void chkmin(T &a, const T &b) { a = a < b ? a : b; }
template<typename T> inline void chkmax(T &a, const T &b) { a = a > b ? a : b; }

const int MAXN = 200005, MAXT = 1 << 19;
char *mp[MAXN], mem[MAXN];
int beg[MAXN], ed[MAXN], sum[MAXT], tag[MAXT], n, m, tot;
vector<int> edge[MAXN];
struct Opt { int l, r, tp; };
vector<Opt> md[MAXN];

void dfs(int u) {
	assert(!beg[u]);
	beg[u] = ++tot;
	for (int v : edge[u]) dfs(v);
	ed[u] = tot;
}

void modify(int a, int b, int t, int l = 1, int r = tot, int k = 1) {
	if (a > r || b < l) return;
	if (a <= l && b >= r) {
		tag[k] += t;
		sum[k] = tag[k] ? r - l + 1 : (l == r ? 0 : sum[k << 1] + sum[k << 1 | 1]);
		return;
	}
	int mid = (l + r) >> 1;
	modify(a, b, t, l, mid, k << 1);
	modify(a, b, t, mid + 1, r, k << 1 | 1);
	sum[k] = tag[k] ? r - l + 1 : sum[k << 1] + sum[k << 1 | 1];
}

int main() {
	scanf("%d%d", &n, &m);
	for (int i = 0; i < n; i++) {
		mp[i] = mem + i * m;
		scanf("%s", mp[i]);
	}
	for (int i = 0; i < n; i++)
	for (int j = 0; j < m; j++) {
		char c = mp[i][j]; int p = i * m + j;
		if (c == 'U' && i + 2 < n)
			edge[(i + 2) * m + j].push_back(p);
		else if (c == 'D' && i > 1)
			edge[(i - 2) * m + j].push_back(p);
		else if (c == 'L' && j + 2 < m)
			edge[p + 2].push_back(p);
		else if (c == 'R' && j > 1)
			edge[p - 2].push_back(p);
		else edge[n * m].push_back(p);
	}
	dfs(n * m);
	assert(tot == n * m + 1);
	for (int i = 0; i < n; i++)
	for (int j = 0; j < m; j++) if (~(i + j) & 1) {
		char c = mp[i][j];
		int p = i * m + j, q;
		if (c == 'U') q = p + m;
		else if (c == 'D') q = p - m;
		else if (c == 'L') q = p + 1;
		else q = p - 1;
		md[beg[p]].push_back(Opt { beg[q], ed[q], 1 });
		md[ed[p] + 1].push_back(Opt { beg[q], ed[q], -1 });
	}
	LL ans = 0;
	for (int i = 1; i <= tot; i++) {
		for (const Opt &o : md[i])
			modify(o.l, o.r, o.tp);
		ans += sum[1];
	}
	printf("%lld\n", ans);
	return 0;
}
···
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值