bzoj 3729: Gty的游戏 (博弈+splay)

本文介绍了一种使用博弈论结合splay树的数据结构来解决一个特定游戏策略问题的方法。给定一棵有根树及各节点上的石子数量,在不超过限定值的情况下,通过移动石子来判断玩家在游戏中的胜负情况。文章详细解释了解题思路和实现细节。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

3729: Gty的游戏

Time Limit: 20 Sec   Memory Limit: 128 MB
Submit: 335   Solved: 104
[ Submit][ Status][ Discuss]

Description

某一天gty在与他的妹子玩游戏。
妹子提出一个游戏,给定一棵有根树,每个节点有一些石子,每次可以将不多于L的石子移动到父节点,询问
将某个节点的子树中的石子移动到这个节点先手是否有必胜策略。
gty很快计算出了策略。
但gty的妹子十分机智,她决定修改某个节点的石子或加入某个新节点。
gty不忍心打击妹子,所以他将这个问题交给了你。
另外由于gty十分绅士,所以他将先手让给了妹子。

Input

第一行两个数字,n和L,n<=5*10^4,L<=10^9
第二行n个数字,表示每个节点初始石子数。
接下来n-1行,每行两个整数u和v,表示有一条从u到v的边。
接下来一行一个数m,表示m组操作。
接下来m行,每行第一个数字表示操作类型
若为1,后跟一个数字v,表示询问在v的子树中做游戏先手是否必胜。
若为2,后跟两个数字x,y表示将节点x的石子数修改为y。
若为3,后跟三个数字u,v,x,表示为u节点添加一个儿子v,初始石子数为x。
在任意时刻,节点数不超过5*10^4。

Output

对于每个询问,若先手必胜,输出"MeiZ",否则输出"GTY"。
另,数据进行了强制在线处理,对于m组操作,除了类型名以外,都需要异或之前回答为"MeiZ"的个数。

Sample Input

2 1000
0 0
1 2
1
1 1

Sample Output

GTY

HINT

Source

[ Submit][ Status][ Discuss]

题解:博弈+splay

每次都只能移动不超过m个,所以sg[x]=mex(i=1..min(x,m) sg[x-i]) sg[0]=0

这个sg值肯定不能直接求,所有如果没有什么公式或模型的话,就只能用找规律大法了。

sg[x]=x%(m+1)

那么这个题就变成了一个阶梯博弈问题,因为要不断的加边,所有我用splay维护dfs,对于每个节点要记录偶层的异或值也要记录奇层的异或值,然后查询的时候分清要奇层还是偶层就可以了。

#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<cstdio>
#define N 200003
using namespace std;
int n,m,tot,point[N],next[N],v[N];
int deep[N],ch[N][3],fa[N],sg[N][3],key[N],l[N],r[N],sit[N];
int q[N],pos[N],mark[N],cnt,root,sz,val[N];
void add(int x,int y)
{
	tot++; next[tot]=point[x]; point[x]=tot; v[tot]=y;
	tot++; next[tot]=point[y]; point[y]=tot; v[tot]=x;
}
void dfs(int x,int f)
{
	deep[x]=deep[f]+1;
	q[++cnt]=x; l[x]=cnt;
	for (int i=point[x];i;i=next[i])
	 if (v[i]!=f) dfs(v[i],x);
	r[x]=++cnt; q[cnt]=-x;
}
void update(int now)
{
	if (mark[now]) sg[now][1]=key[now],sg[now][0]=0;
	else sg[now][0]=key[now],sg[now][1]=0;
	int l=ch[now][0]; int r=ch[now][1];
	sg[now][0]^=sg[l][0]^sg[r][0];
	sg[now][1]^=sg[l][1]^sg[r][1];
}
int get(int x)
{
	return ch[fa[x]][1]==x;
}
void rotate(int x)
{
	int y=fa[x]; int z=fa[y]; int which=get(x);
	ch[y][which]=ch[x][which^1]; fa[ch[x][which^1]]=y;
	fa[y]=x; ch[x][which^1]=y; fa[x]=z;
	if (z) ch[z][ch[z][1]==y]=x;
	update(y); update(x);
}
void splay(int x,int tar)
{
	for (int f;(f=fa[x])!=tar;rotate(x))
	 if (fa[f]!=tar) rotate(get(f)==get(x)?f:x);
	if (!tar) root=x;
}
int build(int l,int r)
{
    if (l>r) return 0;
	int mid=(l+r)/2;
	int now=++sz; 
	if (q[mid]>0) key[now]=val[q[mid]],mark[now]=(deep[q[mid]]&1),sit[mid]=now;
	else key[now]=0,sit[mid]=now;
	ch[now][0]=build(l,mid-1); 
	if (ch[now][0]) fa[ch[now][0]]=now;
	ch[now][1]=build(mid+1,r);
	if (ch[now][1]) fa[ch[now][1]]=now;
	update(now); 
	return now;
}
int pre()
{
	int now=ch[root][0];
	while (ch[now][1]) now=ch[now][1];
	return now;
}
int nxt()
{
	int now=ch[root][1];
	while (ch[now][0]) now=ch[now][0];
	return now;
}
int main()
{
	freopen("a.in","r",stdin);
	freopen("my.out","w",stdout);
	scanf("%d%d",&n,&m);
	for (int i=1;i<=n;i++) 
	 scanf("%d",&val[i]),val[i]%=(m+1);
	for (int i=1;i<n;i++) {
		int x,y; scanf("%d%d",&x,&y);
		add(x,y);
	}
	q[++cnt]=0;
	dfs(1,0); q[++cnt]=0;
	root=build(1,cnt);
	for (int i=1;i<=n;i++) l[i]=sit[l[i]],r[i]=sit[r[i]];
	int ans=0;
	int p; scanf("%d",&p);
	for (int i=1;i<=p;i++) {
		int opt,x,y,k; scanf("%d",&opt);
		if (opt==1) {
			scanf("%d",&x); x^=ans;
			splay(l[x],0); int t1=pre();
			splay(r[x],0); int t2=nxt();
			splay(t1,0);
			splay(t2,root);
			int t=ch[ch[root][1]][0];
		    if (sg[t][(deep[x]&1)^1]) printf("MeiZ\n"),ans++;
		    else printf("GTY\n");
		}
		if (opt==2) {
			scanf("%d%d",&x,&k); x^=ans; k^=ans;
			k%=(m+1); 
			splay(l[x],0); 
			int t1=pre();
			int t2=nxt();
			splay(t1,0);
			splay(t2,root);
			int t=ch[ch[root][1]][0];
			key[t]=k;
			update(t); update(ch[root][1]); update(root);
		}
		if (opt==3) {
			scanf("%d%d%d",&x,&y,&k); x^=ans; y^=ans; k^=ans;
			k%=(m+1);
			deep[y]=deep[x]+1; int now=++sz;
			splay(r[x],0); int t1=pre();
			splay(t1,0);
			splay(r[x],root);
			ch[ch[root][1]][0]=now; fa[now]=ch[root][1];
			key[now]=k; mark[now]=deep[y]&1;
			l[y]=now; r[y]=++sz; ch[now][1]=sz; fa[sz]=now; update(now);
			update(ch[root][1]); update(root);
		}
	}
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值