「CF1368G」Shifting Dominoes 题解
本文网址:https://www.cnblogs.com/zsc985246/p/16869394.html ,转载请注明出处。
题目传送门
题目大意
给你一个 \(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;
}
尾声
如果你发现了问题,你可以直接回复这篇题解
如果你有更好的想法,也可以直接回复!