BZOJ 3729

题面传送门

介于自己以前既没有写过 Staircase-Nim 的题解,也没写过时间轴分块的题解,所以现在就来写一篇吧(fog

首先考虑最极端的情况,如果图是一条链,并且链的一个端点是 (1),那么问题可以转化为序列上的问题,即可视作有一个 (n) 级的阶梯,第 (i) 级台阶上有 (a_i) 个石子,每次可以选择一堆并将其中 (x(xle L)) 个石子向下移动一级,不能移动最底下一级的石子,不能操作者输。

这是一个经典的 staircase 博弈的模型,对于这样的模型我们有一个结论:我们并不用关心奇数级台阶上的石子的情况,只用把偶数级台阶上石子的 SG 值异或起来就是最终游戏的异或值。因为如果先手有必胜策略那么一旦后手将奇数级台阶上的石子移动到了偶数级台阶上,那么先手一定可以将移动的这部分石子再移动到奇数级台阶上,后手有必胜策略的情况也如此。对于此题而言,一堆 (x) 个石子的 SG 值显然是 (xmod(L+1)),因此整个游戏的异或值就是所有 (imod 2=0)(a_imod(L+1)) 的异或和。这个结论放到树上也是成立的,因此对于一次询问 (v) 子树的答案而言,如果子树内所有深度与 (v) 的深度奇偶性相同的节点的 (a_imod(L+1)) 的异或和为 (0) 答案就是 GTR,否则答案是 MeiZ

接下来考虑原问题怎么求解。如果允许离线那我们显然离线建出最终形态的树之后 DFS 序+BIT 即可解决。但是这题不可以离线还要支持插入新点操作,这就使问题变得略有点棘手。LCT 看似可做可我不会。因此考虑 P2137 Gty 的妹子树(怎么又是 Gty/jy)的套路,我们对时间轴分块,每 (sqrt{n}) 次操作重新建树 DFS 一遍,对于已经在重构过的树中的部分的贡献,显然直接 DFS 序+BIT 或者分块即可求出,对于不在重构过的树中的点,我们就暴力开一个 vector,表示上一次重构到这次询问新加入了哪些点,然后每次遍历一遍 vector,在新加入一个点的时候记录一下这个点在重构好的树中哪个点的子树中即可判断该点是否在待查询的点的子树内。如果待查询的点不在重构的树中,那么显然其子树内的点肯定也不在重构好的树中,这样其子树内的点的个数肯定 (sqrt{n}) 级别的。我们就暴力 DFS 找出其子树内的点计算贡献即可。

时间复杂度 (nsqrt{n})

讲个笑话,BZOJ 上不重构,复杂度平方竟然过了(

const int MAXN=1e5;
const int BLK=225;
int n,L,a[MAXN+5];link_list<int,MAXN,MAXN*2> g;
int dep[MAXN+5],bgt[MAXN+5],edt[MAXN+5],tim=0,rid[MAXN+5],con[MAXN+5];
bool fin[MAXN+5];int fa[MAXN+5];
void dfs(int x,int f){
	rid[bgt[x]=++tim]=x;fin[x]=1;con[x]=x;fa[x]=f;
	for(int e=g.hd[x];e;e=g.nxt[e]){
		int y=g.val[e];if(y==f) continue;
		dep[y]=dep[x]+1;dfs(y,x);
	} edt[x]=tim;
}
namespace divblk{
	int blk_sz,blk_cnt,L[322],R[322],bel[MAXN+5];
	int sum_blk[2][MAXN+5],sum_tot[2][322];
	void init(){
		blk_sz=316;blk_cnt=317;
		for(int i=1;i<=blk_cnt;i++){
			L[i]=(i-1)*blk_sz+1;R[i]=min(i*blk_sz,MAXN);
			for(int j=L[i];j<=R[i];j++) bel[j]=i;
		}
	}
	void add(int x,int v,int id){
		for(int i=x;i<=R[bel[x]];i++) sum_blk[id][i]^=v;
		for(int i=bel[x];i<=blk_cnt;i++) sum_tot[id][i]^=v;
	}
	int query(int x,int id){
		if(!x) return 0;
		return sum_blk[id][x]^sum_tot[id][bel[x]-1];
	}
	void clear(){
		memset(sum_blk,0,sizeof(sum_blk));
		memset(sum_tot,0,sizeof(sum_tot));
	}
	void rebuild(int n){
		clear();
		for(int i=1;i<=n;i++){
			sum_blk[dep[rid[i]]&1][i]=a[rid[i]];
			sum_tot[dep[rid[i]]&1][bel[i]]^=a[rid[i]];
		}
		for(int _=0;_<2;_++) for(int i=1;i<=blk_cnt;i++){
			for(int j=L[i]+1;j<=R[i];j++) sum_blk[_][j]^=sum_blk[_][j-1];
			sum_tot[_][i]^=sum_tot[_][i-1];
		}
	}
}
vector<int> nw;
void rebuild(){
	tim=0;memset(fa,0,sizeof(fa));dfs(1,0);
	nw.clear();divblk::rebuild(tim);
}
bool in[MAXN+5];
void dfs_fnd(int x,int f){
	in[x]=1;
	for(int e=g.hd[x];e;e=g.nxt[e]){
		int y=g.val[e];if(y==f) continue;
		dfs_fnd(y,x);
	}
}
void clr(int x,int f){
	in[x]=0;
	for(int e=g.hd[x];e;e=g.nxt[e]){
		int y=g.val[e];if(y==f) continue;
		clr(y,x);
	}
}
bool vis[MAXN+5];
int main(){
	scanf("%d%d",&n,&L);divblk::init();
	for(int i=1;i<=n;i++) scanf("%d",&a[i]),a[i]%=(L+1);
	for(int i=1,u,v;i<n;i++) scanf("%d%d",&u,&v),g.ins(u,v),g.ins(v,u);
	rebuild();
	int cnt=0,qu;scanf("%d",&qu);
	for(int i=1;i<=qu;i++){
		int opt;scanf("%d",&opt);
		if(opt==1){
			int x;scanf("%d",&x);x^=cnt;
			if(fin[x]){
				int res=divblk::query(edt[x],~dep[x]&1)^divblk::query(bgt[x]-1,~dep[x]&1);
				for(int y:nw){
					int z=con[y];
					if(bgt[x]<=bgt[z]&&bgt[z]<=edt[x]&&((dep[y]^dep[x])&1)) res^=a[y];
				} if(!res) puts("GTY");else puts("MeiZ"),cnt++;
			} else {
				int res=0;dfs_fnd(x,fa[x]);
				for(int y:nw){
					if(in[y]&&((dep[y]^dep[x])&1)) res^=a[y];
				} if(!res) puts("GTY");else puts("MeiZ"),cnt++;
				clr(x,fa[x]);
			}
		} else if(opt==2){
			int x,y;scanf("%d%d",&x,&y);x^=cnt;y^=cnt;y%=(L+1);
			if(fin[x]){
				divblk::add(bgt[x],a[x],dep[x]&1);a[x]=y;
//				printf("!!! %d %d %d
",bgt[x],x,y);
				divblk::add(bgt[x],a[x],dep[x]&1);
			} else a[x]=y;
		} else {
			int u,v,y;scanf("%d%d%d",&u,&v,&y);
			u^=cnt;v^=cnt;y^=cnt;y%=(L+1);a[v]=y;fa[v]=u;
			assert(!vis[v]);
			dep[v]=dep[u]+1;con[v]=con[u];nw.pb(v);
			g.ins(u,v);g.ins(v,u);
		} if(i%BLK==0) rebuild();
	}
	return 0;
}
/*
5 3
4 1 2 3 5
1 2
1 3
1 4
1 5
3
1 1
2 4 5
1 0
*/
原文地址:https://www.cnblogs.com/ET2006/p/BZOJ-3729.html