CF1368G Shifting Dominoes (线段树)

题面

有一个 n × m n\times m n×m 的棋盘,被 1 × 2 1\times 2 1×2 的骨牌覆盖,保证 2 ∣ n × m 2|n\times m 2n×m

现在你需要执行以下操作:

移去恰好一个骨牌。
将其他骨牌沿着其长边进行移动。
你需要保证每张骨牌的最终位置与初始位置至少有一个交点。
求你通过若干次操作后可以得到的所有可能的局面的数量。

两种局面不同,当且仅当在其中一者中某个位置被骨牌覆盖,而另一者没有。

1 ≤ n × m ≤ 2 × 1 0 5 1\leq n\times m\leq 2\times10^5 1n×m2×105

题解

这个题要转化很久啊,

不同的局面,翻译过来就是最终两个空块的位置方案

因此我们考虑空块的移动,如果一个多米诺骨牌的一端面向一个空块,就可以通过移动将这个空块的位置变成该骨牌的另一端,于是把它抽象成两个位置之间的一条有向边。一个骨牌可以产生两条有向边,但是每个骨牌只能移动一步,所以对于不小心使用了该骨牌两条有向边的情况,可以等价于最初移去的是该骨牌

对于该有向图还有不错的性质。由于每次移动与上一次位置的曼哈顿距离都是 2,所以假使我们对棋盘进行黑白染色,那么最初的两个空块一定颜色不同,而且只有相同颜色的位置之间有边。我们可以发现,每个点的入度最多为 1,并且可以证明没有环:

如果存在一个环,那么(在原来的棋盘上)环的长度为 4 的倍数,除以 2 后是偶数,由该环组成的封闭图形面积也是偶数,所以由皮克公式 S ( 2 a ) = B 2 ( 2 b ) + I − 1 S(2a)=\frac{B}{2}(2b)+I-1 S(2a)=2B(2b)+I1 可得环内的面积( I I I)为奇数,但是环内不会有空块(两个空块的来源必须得是同一个骨牌),所以环内必须能放满骨牌,这与面积为奇数矛盾,因此不存在环。

所以整个有向图是森林。

一种局面可达,当且仅当这两个位置跑反图分别可达的节点中,存在一对节点是原来某个骨牌的两端。为了方便,我们规定原来某个骨牌两端的点是朋友

我们遍历黑色点的树,然后对于每个点到根的链,我们处理出链上结点的所有朋友跑正图(往叶子方向)可达的节点数,就是这个黑点的贡献。

计算可达的节点数要去重,这不难。我们处理出白森林中每个点的 dfs 序(不同树点 dfs 序不同),放到线段树上去重就是了。

可以用可回退线段树(不好打),也可以用可持久化。

时间复杂度 O ( n m log ⁡ ( n m ) ) O(nm\log(nm)) O(nmlog(nm))

CODE

#include<set>
#include<map>
#include<queue>
#include<stack>
#include<bitset>
#include<random>
#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define MAXN 200005
#define LL long long
#define DB double
#define ENDL putchar('\n')
#define lowbit(x) (-(x) & (x))
#define FI first
#define SE second
#define BI bitset<35>

namespace{ // 良好封装习惯,不怕重名

	LL read() {
		LL f=1,x=0;int s = getchar();if(s < 0) return -1;
		while(s < '0' || s > '9') {if(s == '-')f=-f;s = getchar();}
		while(s >= '0' && s <= '9') {x=x*10+(s^48);s = getchar();}
		return f * x;
	}
	void putpos(LL x) {if(!x)return ;putpos(x/10);putchar('0'+(x%10));}
	void putnum(LL x) {
		if(!x) {putchar('0');return ;}
		if(x<0) {putchar('-');x = -x;}
		return putpos(x);
	}
	void AIput(LL x,int c) {putnum(x);putchar(c);}

	int n,m,s,o,k;
	char ss[MAXN],a[MAXN];
	int id[MAXN],cn,fr[MAXN];
	int to[MAXN];
	int I(int x,int y) {return (x-1)*m+y;}
	int X(int i) {return (i-1)/m + 1;}
	int Y(int i) {return (i-1)%m + 1;}
	int ind[MAXN];
	struct it{
		int ls,rs;
		int nm,lz;
		it(){ls=rs=0;nm=0;lz=0;}
	}tre[MAXN*64];
	int CNT;
	int addtree(int a,int l,int r,int al,int ar) {
		if(l > r || al > r || ar < l || tre[a].lz) return a;
		tre[++ CNT] = tre[a]; a = CNT;
		if(al >= l && ar <= r) {tre[a].lz=1;tre[a].nm = ar-al+1;return a;}
		int md = (al + ar) >> 1;
		tre[a].ls = addtree(tre[a].ls,l,r,al,md);
		tre[a].rs = addtree(tre[a].rs,l,r,md+1,ar);
		tre[a].nm = tre[tre[a].ls].nm + tre[tre[a].rs].nm;
		return a;
	}
	int findtree(int a,int l,int r,int al,int ar) {
		if(l > r || al > r || ar < l || !a) return 0;
		if(tre[a].lz) return min(ar,r) - max(l,al) + 1;
		if(al >= l && ar <= r) return tre[a].nm;
		int md = (al + ar) >> 1;
		return findtree(tre[a].ls,l,r,al,md) + findtree(tre[a].rs,l,r,md+1,ar);
	}
	int hd[MAXN],nx[MAXN];
	void ins(int x,int y) {nx[y] = hd[x];hd[x] = y;}
	int dfn[MAXN],rr[MAXN],tim;
	void dfs0(int x) {
		dfn[x] = ++ tim; for(int i = hd[x];i;i = nx[i]) dfs0(i); rr[x] = tim;
	}
	int rt[MAXN];
	LL dfs(int x,int ff) {
		rt[x] = addtree(rt[ff],dfn[fr[x]],rr[fr[x]],1,tim);
		LL rs = tre[rt[x]].nm;
		for(int i = hd[x];i;i = nx[i]) {
			rs += dfs(i,x);
		}return rs;
	}

}
int main() {
	n = read();m = read();
	for(int i = 1;i <= n;i ++) {
		scanf("%s",ss + 1);
		for(int j = 1;j <= m;j ++) {
			a[I(i,j)] = ss[j];
			if(a[I(i,j)] == 'R') id[I(i,j)] = id[I(i,j-1)] = ++ cn,fr[I(i,j)] = I(i,j-1),fr[I(i,j-1)] = I(i,j);
			else if(a[I(i,j)] == 'D') id[I(i,j)] = id[I(i-1,j)] = ++ cn,fr[I(i,j)] = I(i-1,j),fr[I(i-1,j)] = I(i,j);
		}
	}
	for(int i = 1;i <= n;i ++) {
		for(int j = 1;j <= m;j ++) {
			if(a[I(i,j)] == 'U') {
				if(i+2 <= n) to[I(i,j)] = I(i+2,j);
			}
			else if(a[I(i,j)] == 'D') {
				if(i-2 > 0) to[I(i,j)] = I(i-2,j);
			}
			else if(a[I(i,j)] == 'L') {
				if(j+2 <= m) to[I(i,j)] = I(i,j+2);
			}
			else if(j-2 > 0) to[I(i,j)] = I(i,j-2);
		}
	}
	for(int i = 1;i <= n*m;i ++) {
		if(to[i]) ins(to[i],i);
	}
	for(int i = 1;i <= n*m;i ++) {
		if((X(i)+Y(i)) % 2 == 0) {
			if(!to[i]) dfs0(i);
		}
	}
	LL ans = 0;
	for(int i = 1;i <= n*m;i ++) {
		if((X(i)+Y(i)) & 1) {
			if(!to[i]) ans += dfs(i,0);
		}
	}
	AIput(ans,'\n');
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值