「CF1368G」Shifting Dominoes 题解

本文网址:https://www.cnblogs.com/zsc985246/p/16869394.html ,转载请注明出处。

题目传送门

「CF1368G」Shifting Dominoes

题目大意

给你一个 \(n \times m\) 的棋盘,上面有 \(\frac{n \times m}{2}\) 个不重叠的骨牌,你可以删除一个骨牌,并将剩下的骨牌移动。每个骨牌最多移动一格,求棋盘最终局面的方案数。

两个局面不同指两个空位的位置不同。

输入

第一行两个整数 \(n,m\)

接下来 \(n\) 行,每行一个长度为 \(m\) 且由 \(U,D,R,L\) 的字符串。为 \(U,D,R,L\) 分别表示被骨牌的上、下、左、右半部分覆盖。

输出

一行一个整数,表示方案数。

思路

问题转化

首先可以发现,删掉一个骨牌后,移动剩下的骨牌可以看作是在移动空位。并且可以发现必定是朝一个方向移动 2 格。这提示我们可以建图。只需要按照空位的移动连边即可。举个例子:

建边

看起来这样建图有一点问题:题目要求每个骨牌最多移动一格。但是我们画图发现:

转化

其实一个骨牌移动 2 格就等于移除这一个骨牌

所以其实这样建图没有问题。

我们可以大胆猜测这张图没有环。考虑证明:

当图中存在环时,即出现下图情况:

环

此时我们可以将上图抽象为下图:

抽象后的环

可以发现,连续红色格子的个数必定是奇数,否则构不成环。

所以橙色长方形的边长为奇数,即橙色长方形的格子数也是奇数。

同理可以发现,黄色格子个数为偶数。

红色外框格子个数(骨牌放置的位置)为偶数。

所以绿色格子数(橙色长方形格子数-黄色格子数-红色外框格子数)是奇数。

显然奇数是不能用骨牌铺满的。与题意矛盾。

所以图中不存在环。

即我们建出的图是一个森林。

删掉一个骨牌后的两个点显然不在一个子树,所以问题转化为:给定一个森林,求从不同的树中选两个点的方案数。

记树上节点 \(x\) 的子树大小为 \(siz_x\),则对于每个骨牌的两个节点 \(x,y\),对答案的贡献为 \(siz_x \times siz_y\)

但是这样统计会有重复。

树上方案数去重,考虑运用 dfs 序的神奇性质。

说到 dfs 序,又有乘积,难想到将乘积的形式转化为面积。

记书上节点 \(x\) 的 dfs 序为 \(dfn_x\)

转化后即下图所示:

转化为矩阵面积

接下来就可以用扫描线求解答案了。

代码实现

#include<bits/stdc++.h>
#define ll int
const ll N=200010;
using namespace std;

ll n,m;
char S[N];
long long ans;
vector<ll>a[N];//注意用 vector 
vector<ll>e[N];
vector<pair<ll,ll>>t[N];
ll tmp,dfn[N],size[N];
ll cnt,vis[N];
ll b[N];

struct LINE{//线 
	ll x;//横坐标 
	ll y1,y2;//纵坐标区间 
	ll p;//标记 
	bool operator<(LINE a){return x<a.x||x==a.x&&p>a.p;}
}d[N];

struct NODE{//线段树 
	ll cnt,len;
}s[N*8];

ll id(ll x,ll y){//计算编号 
	return (x-1)*m+y;
}

void add(ll x,ll y){//加边 
	e[x].push_back(y);
	vis[y]=1;
}

void dfs(ll x){//计算 size 和 dfn 
	size[x]=1;
	dfn[x]=++tmp;
	for(ll y:e[x]){
		dfs(y);
		size[x]+=size[y];
	}
}

void pushup(ll rt,ll l,ll r){
	if(s[rt].cnt>0)s[rt].len=r-l+1;
	else s[rt].len=s[rt<<1].len+s[rt<<1|1].len;
}

void change(ll rt,ll l,ll r,ll x,ll y,ll v){
	if(x>r||y<l)return;
	ll mid=(l+r)>>1;
	if(x<=l&&r<=y){
		s[rt].cnt+=v;
		pushup(rt,l,r);
		return;
	}
	change(rt<<1,l,mid,x,y,v);
	change(rt<<1|1,mid+1,r,x,y,v);
	pushup(rt,l,r);
}

int main(){
	
	//输入和预处理 
	scanf("%d%d",&n,&m);
	for(ll i=0;i<=n+1;i++){
		a[i].resize(m+3);
	}
	for(ll i=1;i<=n;i++){
		scanf("%s",S+1);
		for(int j=1;j<=m;++j){
			if(S[j]=='U')a[i][j]=1;
			if(S[j]=='D')a[i][j]=2;
			if(S[j]=='L')a[i][j]=3;
			if(S[j]=='R')a[i][j]=4;
		}
	}
	//建图 
	for(ll i=1;i<=n;i++){
		for(ll j=1;j<=m;j++){
			if(j+2<=m&&a[i][j+1]==3)add(id(i,j),id(i,j+2));
			if(j-2>=1&&a[i][j-1]==4)add(id(i,j),id(i,j-2));
			if(i+2<=n&&a[i+1][j]==1)add(id(i,j),id(i+2,j));
			if(i-2>=1&&a[i-1][j]==2)add(id(i,j),id(i-2,j));
			if(!b[id(i,j)]){
				b[id(i,j)]=++cnt;
				if(a[i][j]==3)b[id(i,j+1)]=cnt;
				if(a[i][j]==1)b[id(i+1,j)]=cnt;
			}
			t[b[id(i,j)]].push_back(make_pair(i,j));
		}
	}
	//求 dfn 
	for(ll i=1;i<=n;i++){
		for(ll j=1;j<=m;j++){
			if(!vis[id(i,j)]){
				dfs(id(i,j));
			}
		}
	}
	//转化为矩阵 
	for(ll i=1;i<=cnt;i++){
		ll x1=t[i][0].first,y1=t[i][0].second;
		ll x2=t[i][1].first,y2=t[i][1].second;
		ll u=id(x1,y1),v=id(x2,y2);
		ll l1=dfn[u],r1=dfn[u]+size[u]-1;
		ll l2=dfn[v],r2=dfn[v]+size[v]-1;
		if((x1+y1)&1)swap(l1,l2),swap(r1,r2);
		d[i*2-1]={l1,l2,r2,1};
		d[i*2]={r1+1,l2,r2,-1};
	}
	cnt*=2;
	//排序 
	sort(d+1,d+cnt+1);
	//扫描线 
	for(ll i=1;i<=cnt;i++){
		if(i>1&&d[i].x>d[i-1].x)ans+=(d[i].x-d[i-1].x)*s[1].len;
		change(1,1,n*m,d[i].y1,d[i].y2,d[i].p);
	}
	printf("%lld",ans);
	
	return 0;
}

尾声

如果你发现了问题,你可以直接回复这篇题解

如果你有更好的想法,也可以直接回复!

posted @ 2022-11-08 13:37  zsc985246  阅读(203)  评论(0编辑  收藏  举报