[NOIP2021] 棋局 题解
[NOIP2021] 棋局 题解
知识点
并查集,启发式合并,线段树合并。
分析
这可以算码农题了,码量大,不过还算好调。(不过在考场上没把握是肯定不敢写正解的。)
首先有一个十分明显的并查集套路:操作离线再倒过来,把分裂变成合并。然后我们只要在合并时顺便查询即可。
思考计数方式显然是这题最重要的部分,这个思考完了之后,其他的都会一目了然。此外还有一些细节,如离散化,合并等。
离散化
我们发现等级 \(\{lv_i\}\) 相同的在倒过来时有可能会记多,这个时候,我们就需要对等级 \(\{lv_i\}\) 离散化。
对于两个等级相同的点,它们虽然可以互通,但是由于加入是有序的,所以只有前面加入的对后面的有贡献。依此,我们以等级 \(\{lv_i\}\) 和加入时间 \(\{tim_i\}\) 为关键字排序离散化等级即可。
计数
那么我们考虑如何计数才能避免重复,首先处理麻烦的,这样会好些很多。
互通道路
第一步先用并查集把空着的地方合并了,再维护它的大小 \(siz\),然后线段树合并记各个等级有几个,在这里查询一下等级小于等于它的有多少即可。发现还有一个颜色的限制,但是一共只有两种颜色,所以各开一棵线段树来合并即可。
那么这里一共需要用到一个并查集、两棵可合并的线段树。
直行道路
依照与部分分中一样的方法对四个方向各开一个并查集,然后就可以快速查询最远能到哪里。
把这些都加入后,考虑去掉与互通道路重复的部分,我们依旧可以开一个可合并的线段树,然后记一下这一段有哪些是属于互通道路的连通块的,在对直行道路计数时减掉互通道路处记过的即可。
在这里有一个问题:无法很好的连续查询线段树上的区间。为了解决这个问题,我们可以把编号方式改变一下:分别开两种编号,一种依照行,一种依照列,以下是编号函数:
int R(const int x,const int y) {
return (x-1)*m+y;
}
int C(const int x,const int y) {
return x+(y-1)*n;
}
然后对每种编号方式都开一棵可合并的线段树,再维护即可。当然,并查集的编号方式也要变成相应的。
那么这里一共需要用到四个并查集、两棵可合并的线段树。
普通道路
四周直接枚举特判一下即可。
合并
我们合并时的操作都是按照计数方面来的,所以一个个对着来就不会有太大问题。
首先我们枚举四个方向依据道路类型来合并:设现在在点 \(u\),下一个为点 \(v\)。
互通道路
还要再分两种情况来看:
-
\(v\) 还有棋子:
如果 \(v\) 原本不在与 \(u\) 连通的互通道路上,把它加入互通道路维护的对应颜色的线段树中;
-
\(v\) 没有棋子:
如果两者本不能通过互通道路相连,那么先在对方互通道路维护的对应颜色的线段树中减掉自己,再在互通道路维护的并查集和所有线段树中把他们合并。
直行道路
在直行道路中维护的并查集中将他们连起来,要注意判断反向的边能不能也连起来。
在上面的操作都做完了之后,记得在直行道路中维护的线段树中把自己标记上。
查询
发现不好直接把其他没有棋子的点都初始化了,那么就把这些点也一起当询问点删了,当做初始化。
代码
时空复杂度:\(O(nm\log_2{(nm)})\)。
#define Plus_Cat "chess"
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define ll long long
#define RCL(a,b,c,d) memset(a,b,sizeof(c)*(d))
#define FOR(i,a,b) for(int i(a);i<=(int)(b);++i)
#define DOR(i,a,b) for(int i(a);i>=(int)(b);--i)
#define tomin(a,...) ((a)=min({(a),__VA_ARGS__}))
#define tomax(a,...) ((a)=max({(a),__VA_ARGS__}))
using namespace std;
namespace IOstream {
#define getc() getchar()
#define putc(ch) putchar(ch)
#define isdigit(ch) ('0'<=(ch)&&(ch)<='9')
#define blank(ch) ((ch)==' '||(ch)=='\n'||(ch)=='\r'||(ch)==(EOF))
template<class T>void rd(T &x) {
static bool sign(0);
static char ch(0);
sign=0;
while(!('0'<=(ch=getc())&&(ch)<='9'))if(ch=='-')sign=1;
x=ch^48;
while('0'<=(ch=getc())&&(ch)<='9')x=(x<<1)+(x<<3)+(ch^48);
return x=sign?-x:x,void();
}
template<class T>void wr(const T x,const char End='\n') {
static short top(0);
static short st[10];
T y(x);
if(y<0)putc('-'),y=-y;
do st[++top]=y%10,y/=10;
while(y);
while(top)putc(st[top]^'0'),--top;
return putc(End),void();
}
} using namespace IOstream;
constexpr int N(2e5+10),dx[4] {-1,1,0,0},dy[4] {0,0,-1,1};
int Cas,n,m,s,Q;
int ans[N],col[N],idx[N],lev[N],tim[N];
int sta[N][4];
struct node {
int x,y;
node(int x=0,int y=0):x(x),y(y) {}
bool check() {
return x>0&&x<=n&&y>0&&y<=m;
}
int I() {
return (x-1)*m+y;
}
int R() {
return (x-1)*m+y;
}
int C() {
return x+(y-1)*n;
}
node move(int d) {
return node(x+dx[d],y+dy[d]);
}
} qr[N],pla[N];
struct DSU {
int fa[N],siz[N];
int &operator()(int i) {
return fa[i];
}
int &operator[](int i) {
return siz[i];
}
void Init(int n=s) {
FOR(i,1,n)siz[fa[i]=i]=1;
}
int get(int x) {
return fa[x]^x?fa[x]=get(fa[x]):x;
}
} st[4],con;
struct SEG {
int tot;
int rt[N];
struct node {
int ls,rs,sum;
node(int ls=0,int rs=0,int sum=0):ls(ls),rs(rs),sum(sum) {}
} tr[N<<6];
#define ls(p) (tr[p].ls)
#define rs(p) (tr[p].rs)
#define mid ((l+r)>>1)
int &operator[](int i) {
return rt[i];
}
void Init(int n=s) {
tr[tot=0]=node(),RCL(rt+1,0,int,n);
}
void Up(int p) {
tr[p].sum=tr[ls(p)].sum+tr[rs(p)].sum;
}
void Plus(int x,int d,int &p,int l=1,int r=s) {
if(!p)tr[p=++tot]=node();
if(l==r)return tr[p].sum+=d,void();
return x<=mid?Plus(x,d,ls(p),l,mid):Plus(x,d,rs(p),mid+1,r),Up(p);
}
int Sum(int L,int R,int p,int l=1,int r=s) {
if(!p||(L<=l&&r<=R))return tr[p].sum;
return (L<=mid?Sum(L,R,ls(p),l,mid):0)+(mid<R?Sum(L,R,rs(p),mid+1,r):0);
}
void Merge(int &p,int q,int l=1,int r=s) {
if(!p||!q)return p|=q,void();
if(l==r)return tr[p].sum|=tr[q].sum,void();
Merge(ls(p),ls(q),l,mid),Merge(rs(p),rs(q),mid+1,r),Up(p);
}
#undef ls
#undef rs
#undef mid
} St[2],Con[2];
int I(const int x,const int y) {
return (x-1)*m+y;
}
int R(const int x,const int y) {
return (x-1)*m+y;
}
int C(const int x,const int y) {
return x+(y-1)*n;
}
int Del(node u) {
const int uI(u.I()),uR(u.R()),uC(u.C());
/*Merge*/
FOR(d,0,3)if(sta[uI][d]>1) {
node v(u.move(d));
if(!v.check())continue;
const int vI(v.I()),vR(v.R()),vC(v.C());
if(sta[uI][d]==2) {
st[d](uI)=vI;
if(!lev[vI])st[d^1](vI)=uI;
continue;
}
if(lev[vI]) {
if(St[0].Sum(vI,vI,St[0][uI]))continue;
Con[col[vI]].Plus(lev[vI],1,Con[col[vI]][uI]);
St[0].Plus(vR,1,St[0][uI]),St[1].Plus(vC,1,St[1][uI]);
continue;
}
int pa(con.get(vI));
if(uI==pa)continue;
con(pa)=uI,con[uI]+=con[pa],Con[col[uI]].Plus(lev[uI],-1,Con[col[uI]][pa]);
St[0].Plus(uR,-1,St[0][pa]),St[1].Plus(uC,-1,St[1][pa]);
St[0].Merge(St[0][uI],St[0][pa]),St[1].Merge(St[1][uI],St[1][pa]);
Con[0].Merge(Con[0][uI],Con[0][pa]),Con[1].Merge(Con[1][uI],Con[1][pa]);
}
St[0].Plus(uR,1,St[0][uI]),St[1].Plus(uC,1,St[1][uI]);
/*Count*/
int res(con[uI]-1+Con[col[uI]^1].Sum(1,lev[uI],Con[col[uI]^1][uI])),vI(st[0].get(uI));
if(uI^vI) {
node &v(pla[vI]);
const int vC(v.C());
res+=uC-vC;
if(vC+1<=uC-1)res-=St[1].Sum(vC+1,uC-1,St[1][uI]);
if((lev[vI]&&col[uI]==col[vI])||lev[vI]>lev[uI])--res;
else res-=St[1].Sum(vC,vC,St[1][uI]);
}
vI=st[1].get(uI);
if(uI^vI) {
node &v(pla[vI]);
const int vC(v.C());
res+=vC-uC;
if(uC+1<=vC-1)res-=St[1].Sum(uC+1,vC-1,St[1][uI]);
if((lev[vI]&&col[uI]==col[vI])||lev[vI]>lev[uI])--res;
else res-=St[1].Sum(vC,vC,St[1][uI]);
}
vI=st[2].get(uI);
if(uI^vI) {
node &v(pla[vI]);
const int vR(v.R());
res+=uR-vR;
if(vR+1<=uR-1)res-=St[0].Sum(vR+1,uR-1,St[0][uI]);
if((lev[vI]&&col[uI]==col[vI])||lev[vI]>lev[uI])--res;
else res-=St[0].Sum(vR,vR,St[0][uI]);
}
vI=st[3].get(uI);
if(uI^vI) {
node &v(pla[vI]);
const int vR(v.R());
res+=vR-uR;
if(uR+1<=vR-1)res-=St[0].Sum(uR+1,vR-1,St[0][uI]);
if((lev[vI]&&col[uI]==col[vI])||lev[vI]>lev[uI])--res;
else res-=St[0].Sum(vR,vR,St[0][uI]);
}
FOR(d,0,3)if(sta[uI][d]==1) {
node v(u.move(d));
if(!v.check())continue;
const int vI(v.I());
if(!((lev[vI]&&col[uI]==col[vI])||lev[vI]>lev[uI]))res+=!St[0].Sum(vI,vI,St[0][uI]);
}
return lev[uI]=0,res;
}
int Cmain() {
/*Scan*/
rd(n),rd(m),rd(Q),RCL(tim+1,0,int,s=n*m);
FOR(i,1,s)idx[i]=i,lev[i]=1;
FOR(x,1,n)FOR(y,1,m)pla[I(x,y)]=node(x,y);
FOR(x,1,n)FOR(y,1,m-1) {
char ch(getc());
while(!isdigit(ch))ch=getc();
sta[I(x,y)][3]=sta[I(x,y+1)][2]=(ch^48);
}
FOR(x,1,n-1)FOR(y,1,m) {
char ch(getc());
while(!isdigit(ch))ch=getc();
sta[I(x,y)][1]=sta[I(x+1,y)][0]=(ch^48);
}
FOR(i,1,Q) {
int Col,Idx,Lev;
rd(Col),rd(Lev),rd(qr[i].x),rd(qr[i].y),col[Idx=qr[i].I()]=Col,lev[Idx]=Lev,tim[Idx]=i;
}
/*Init*/
st[0].Init(),st[1].Init(),st[2].Init(),st[3].Init(),con.Init();
St[0].Init(),St[1].Init(),Con[0].Init(),Con[1].Init();
/*Sort*/
sort(idx+1,idx+s+1,[](int x,int y) {
return lev[x]^lev[y]?lev[x]<lev[y]:tim[x]<tim[y];
});
/*Unique*/
FOR(i,1,s)lev[idx[i]]=i;
/*Delete*/
FOR(x,1,n)FOR(y,1,m)if(!tim[I(x,y)])Del(node(x,y));
/*Query*/
DOR(i,Q,1)ans[i]=Del(qr[i]);
/*Print*/
FOR(i,1,Q)wr(ans[i]);
return 0;
}
int main() {
#ifdef Plus_Cat
freopen(Plus_Cat ".in","r",stdin),freopen(Plus_Cat ".out","w",stdout);
#endif
for(rd(Cas); Cas; --Cas)Cmain();
return 0;
}