[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\)

互通道路

还要再分两种情况来看:

  1. \(v\) 还有棋子:

    如果 \(v\) 原本不在与 \(u\) 连通的互通道路上,把它加入互通道路维护的对应颜色的线段树中;

  2. \(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;
}
posted @ 2024-11-13 13:57  plus_cat  阅读(11)  评论(0编辑  收藏  举报