[JZOJ4759] 【雅礼联考GDOI2017模拟9.4】石子游戏

题目

描述

题目大意

在一棵树上,每个节点都有些石子。
每次将 m m m颗石子往上移,移到根节点就不能移了。
双方轮流操作,问先手声还是后手胜。
有三种操作:
1、 询问以某个节点为根的答案。
2、 改变某个点的石子数。
3、 在树中加入一个点。


思考历程

这是一道博弈题。
意味着我连暴力都不会打。
所以放弃治疗。


正解

首先,偶数层的石子是没有意义的。
如果移动了偶数层的石子,另一方就可以模仿你的操作,继续移动这颗石子。
所以我们只需要考虑奇数层的石子,每次移动 1 1 1 m m m颗石子,移动到上一层之后消失。
这就转化成了一个Nimk问题。
按照套路,将每个石子数模 m + 1 m+1 m+1(个人感性理解:当你移动 1 1 1 m m m颗石子的时候,别人可以移动石子使得你们移的总数为 m + 1 m+1 m+1
然后异或起来,如果为 0 0 0就必败,反之必胜。

然后这题就差不多做完了。
我们只需要维护子树中偶数层的异或和就可以了。
LCT?ETT?
都行。
在维护的时候可以分别建立两棵树,以 1 1 1号点为准,偶数层和奇数层分别建一棵树。
对于某个节点,如果它在奇数层,那么奇数层的树中它有值,偶数层的树中值为 0 0 0
反之亦然。
这样处理起来就比较方便了。

话说这题真的有毒,说好保证编号不超过 50000 50000 50000,结果真香了。
害得我调试了至少一天半……
开到 60000 60000 60000就能过了。


代码

using namespace std;
#include 
#include 
#include 
#include 
#define N 60010
struct Node *null; 
struct Node{
	Node *fa,*c[2];
	bool is_root,rev;
	int sum,su;
	inline bool getson(){return fa->c[0]!=this;}
	inline void update(){
		sum=c[0]->sum^c[1]->sum^su;
	}
	inline void reserve(){
		swap(c[0],c[1]);
		rev^=1;
	}
	inline void pushdown(){
		if (rev){
			c[0]->reserve();
			c[1]->reserve();
			rev=0;
		}
	}
	inline void push(){
		if (!is_root)
			fa->push();
		pushdown();
	}
	inline void rotate(){
		Node *y=fa,*z=y->fa;
		if (y->is_root){
			y->is_root=0;
			is_root=1;
		}
		else
			z->c[y->getson()]=this;
		bool k=getson();
		fa=z;
		y->c[k]=c[k^1];
		c[k^1]->fa=y;
		c[k^1]=y;
		y->fa=this;
		sum=y->sum;
		y->update();
	}
	inline void splay(){
		push();
		while (!is_root){
			if (!fa->is_root){
				if (getson()!=fa->getson())
					rotate();
				else
					fa->rotate();
			}
			rotate();
		}
	}
	inline Node *access(){
		Node *x=this,*y=null;
		for (;x!=null;y=x,x=x->fa){
			x->splay();
			x->su^=y->sum^x->c[1]->sum;
			x->c[1]->is_root=1;
			x->c[1]=y;
			y->is_root=0;
			x->update();
		}
		return y;
	}
	inline void mroot(){
		access()->reserve();
	}
	inline void link(Node *y){
		access();
		splay();
		y->mroot();
		y->splay();
		y->fa=this;
		su^=y->sum;
		update();
	}
} d[2][N];
int n,m;
int a[N];
struct EDGE{
	int to;
	EDGE *las;
} e[N*2];
int ne;
EDGE *last[N];
bool col[N];
void build(int x,int fa){
	d[col[x]][x]={null,null,null,1,0,0,a[x]};
	d[col[x]^1][x]={null,null,null,1,0,0,0};
	for (EDGE *ei=last[x];ei;ei=ei->las)
		if (ei->to!=fa){
			col[ei->to]=col[x]^1;
			build(ei->to,x);
			d[0][x].link(&d[0][ei->to]);
			d[1][x].link(&d[1][ei->to]);
		}
}
int main(){
	null=new Node;
	*null={null,null,null,0,0,0,0};
	scanf("%d%d",&n,&m);
	for (int i=1;i<=n;++i)
		scanf("%d",&a[i]),a[i]%=m+1;
	for (int i=1;i<n;++i){
		int u,v;
		scanf("%d%d",&u,&v);
		last[u]=&(e[++ne]={v,last[u]});
		last[v]=&(e[++ne]={u,last[v]});
	}
	build(1,0);
	d[0][1].mroot(),d[1][1].mroot();
	int T,lastans=0;
	scanf("%d",&T);
	while (T--){
		int op;
		scanf("%d",&op);
		if (op==1){
			int x;
			scanf("%d",&x),x^=lastans;
			Node *p=&d[col[x]^1][x];
			p->access();
			p->splay();
			if (p->sum^p->c[0]->sum)
				printf("Yes\n"),lastans++;
			else
				printf("No\n");
		}
		else if (op==2){
			int x,y;
			scanf("%d%d",&x,&y),x^=lastans,y^=lastans;
			y%=m+1;
			Node *p=&d[col[x]][x];
			p->access();
			p->splay();
			p->su^=a[x]^y;
			a[x]=y;
			p->update();
		}
		else{
			int u,v,x;
			scanf("%d%d%d",&u,&v,&x),u^=lastans,v^=lastans,x^=lastans;
			x%=m+1;
			a[v]=x;
			col[v]=col[u]^1;
			d[col[v]][v]={null,null,null,1,0,0,x};
			d[col[v]^1][v]={null,null,null,1,0,0,0};
			d[0][u].link(&d[0][v]);
			d[1][u].link(&d[1][v]);
		}
	}
	return 0;
}

整篇似乎也没什么需要注释的……


总结

像这样的博弈类问题普遍有一个套路。
分为奇数层和偶数层,就可以不管偶数层。
然后直接处理奇数层即可。

你可能感兴趣的:(LCT,博弈)