CF1368G - Shifting Dominoes
题目大意
给定一个被(1 imes 2)的骨牌(横向或者竖向)铺满的方格图
现在可以拿走一个骨牌,之后任意一个骨牌可以沿着其放置方向左右移动至多一步
求最终两个空位所在不同位置的方案数
分析
观察一个空位的移动
如果上/下/左/右边是一条骨牌,则可以移动到该骨牌所在的上/下/左/右边方格
将这个移动方式构成一个有向图(当然忽略反复横跳的情况)
大胆猜测此时你会发现它是 两片外向树森林,下面说明三个充分条件
1.跳跃的过程为((x,y) ightarrow (xpm 2,ypm 2)),显然(x+y)的奇偶性不变,故可以黑白染色分为两部分
2.一个点至多有一条入边:一个点的入边只能来自其所在骨牌的另一边
3.图中不存在环:
假设构成了一个环,此时这些边对应的骨牌围成一个不规则的环
从环的某一个角出发,向四周走,发现其余所有点总能完成一一匹配
也就是说,环内部包含的点个数为奇数,显然不存在这样的覆盖方案
答案计算
考虑移除一张骨牌生成两个点((x,y)),两个点分属于两片森林,并且可以向下走
不妨求出森林的( ext{dfs})序,此时问题变成了一个二维空间矩形覆盖问题
可以扫描线+线段树解决
const int N=2e5+10;
int n,m,d;
string s[N];
int I(int x,int y){ return (x-1)*m+y; }
ll ans;
vector <int> G[N];
int col(int u){ return ((u-1)%m+(u-1)/m)&1; }
void Link(int u,int v){ G[u].pb(v),ind[v]++; }
int ind[N],L[N],R[N],dfn;
void dfs(int u,int f){
L[u]=++dfn;
for(int v:G[u]) if(v!=f) dfs(v,u);
R[u]=dfn;
}
// 线段树维护扫描过程中第二维未被覆盖的点个数
struct Node{
int mi,x;
Node operator + (const Node _) const {
Node res;
res.mi=min(mi,_.mi),res.x=0;
if(mi==res.mi) res.x+=x;
if(_.mi==res.mi) res.x+=_.x;
return res;
}
} tr[N<<2];
int t[N<<2];
void Down(int p){
if(!t[p]) return;
rep(i,p<<1,i+1) t[i]+=t[p],tr[i].mi+=t[p];
t[p]=0;
}
void Upd(int p,int l,int r,int ql,int qr,int x){
if(ql<=l && r<=qr) {
t[p]+=x,tr[p].mi+=x;
return;
}
Down(p);
int mid=(l+r)>>1;
if(ql<=mid) Upd(p<<1,l,mid,ql,qr,x);
if(qr>mid) Upd(p<<1|1,mid+1,r,ql,qr,x);
tr[p]=tr[p<<1]+tr[p<<1|1];
}
struct Update{
int p,l,r,x;
bool operator < (const Update __) const { return p<__.p; }
} U[N*2];
int C;
void Add(int x,int y){
if(col(x)) swap(x,y);
U[++C]=(Update){L[x],L[y],R[y],1};
U[++C]=(Update){R[x]+1,L[y],R[y],-1};
}
void Build(int p,int l,int r){
tr[p]=(Node){0,r-l+1};
if(l==r) return;
int mid=(l+r)>>1;
Build(p<<1,l,mid),Build(p<<1|1,mid+1,r);
}
int main(){
n=rd(),m=rd();
rep(i,1,n) {
cin>>s[i];
rep(j,1,m) {
if(s[i][j-1]=='U') if(i>1) Link(I(i-1,j),I(i+1,j));
if(s[i][j-1]=='D') if(i<n) Link(I(i+1,j),I(i-1,j));
if(s[i][j-1]=='L') if(j>1) Link(I(i,j-1),I(i,j+1));
if(s[i][j-1]=='R') if(j<m) Link(I(i,j+1),I(i,j-1));
}
}
// 获得dfs序
rep(i,1,n*m) if(!ind[i]) dfs(i,0);
rep(i,1,n) {
rep(j,1,m) {
if(s[i][j-1]=='U') Add(I(i,j),I(i+1,j));
if(s[i][j-1]=='L') Add(I(i,j),I(i,j+1));
}
}
Build(1,1,dfn);
sort(U+1,U+C+1);
int p=1;
rep(i,1,dfn) {
while(p<=C && U[p].p<=i) Upd(1,1,dfn,U[p].l,U[p].r,U[p].x),p++;
int c=dfn-(tr[1].mi==0?tr[1].x:0);
ans+=c;
}
printf("%lld
",ans);
}