给定一颗树,初始n个结点,1为根节点。每个结点上有一定的石子数。
现在你需要在线兹瓷三种操作:
1、询问以x为根的子树中进行组合游戏,双方轮流操作,每次操作可以将一个结点(在子树内且不为x)的不超过p个至少1个石子移至其父亲结点。问这个游戏先手是否必胜?
2、修改一个结点的石子数。
3、新建一个结点石子数为x,其父亲设为y(保证y已经建立)
我们需要解决以下两个博弈论问题:
1、nim游戏的改版,每次最多拿走m个石子。
那么 sg[i]=mexmin(m,i)i=1sg[i−m]
找规律及感性认识得 sg[i]=i%(m+1)
那我们干脆把初始石子数直接模m+1变为普通Nim游戏。
2、有n级台阶,从0级开始数到n级。每级上都有一定得石子。每次可以把一个阶梯的石子往下移,0级阶梯的不能移,不能操作者输。
这个比较机智!结论是:我们只在乎奇数层的石子数,然后把每个奇数层当作一堆石子,那么该游戏等价于nim游戏。
为什么呢?
移动偶数层的石子,我们可以把该层下的石头拿等量继续往下,相当于这些石子还在偶数层。移动奇数层的石子,我们视为它消失了。
于是就是经典nim游戏了。
我们用dfs序来表示这个树。
那么询问操作相当于询问区间深度与x深度奇偶性不同的数的nim和,修改操作就直接改,添加操作就是在一个数后添加一个数。
我们用splay维护dfs序,现在的问题是如何获取询问操作询问的是哪一个区间?
左端点肯定是x,右端点呢?传统做法应该维护size,但那样我们需要兹瓷size的动态维护涉及链修改,这里可以利用splay搞一波。
结论:在最后添加虚拟结点深度设为0,那么对于x,找到其在dfs序右边最近的一个点y满足d[y]<=d[x],那么[x,y)即是询问区间。至于为什么要虚拟结点这个自己yy即可得。
那么用splay维护区间深度为奇数的点nim和、所有点的nim和、所有点深度最小值即可。
#include<cstdio>
#include<algorithm>
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
typedef long long ll;
const int maxn=100000+10;
int key[maxn],d[maxn],father[maxn],tree[maxn][2],sum[maxn],num[maxn],cnt[maxn];//sum quan xor num ji xor cnt mind
int h[maxn],go[maxn*2],next[maxn*2],id[maxn];
int i,j,k,l,t,n,m,p,tot,top,root,mask;
void add(int x,int y){
go[++top]=y;
next[top]=h[x];
h[x]=top;
}
void update(int x){
sum[x]=sum[tree[x][0]]^sum[tree[x][1]]^key[x];
num[x]=num[tree[x][0]]^num[tree[x][1]];
if (d[x]%2) num[x]^=key[x];
cnt[x]=d[x];
if (tree[x][0]) cnt[x]=min(cnt[x],cnt[tree[x][0]]);
if (tree[x][1]) cnt[x]=min(cnt[x],cnt[tree[x][1]]);
}
int pd(int x){
if (x==tree[father[x]][0]) return 0;else return 1;
}
void rotate(int x){
int y=father[x],z=pd(x);
father[x]=father[y];
if (father[y]) tree[father[y]][pd(y)]=x;
tree[y][z]=tree[x][1-z];
if (tree[x][1-z]) father[tree[x][1-z]]=y;
tree[x][1-z]=y;
father[y]=x;
update(y);
update(x);
}
void splay(int x,int y){
while (father[x]!=y){
if (father[father[x]]!=y)
if (pd(x)==pd(father[x])) rotate(father[x]);else rotate(x);
rotate(x);
}
}
void dfs(int x,int y){
if (y) d[x]=d[y]+1;
tree[root][1]=x;
father[x]=root;
update(root);
splay(x,0);
root=x;
int t=h[x];
while (t){
if (go[t]!=y) dfs(go[t],x);
t=next[t];
}
}
int find(int x,int y){
if (tree[x][0]&&cnt[tree[x][0]]<=y) return find(tree[x][0],y);
else if (d[x]<=y) return x;
else return find(tree[x][1],y);
}
int main(){
//freopen("gty.in","r",stdin);
scanf("%d%d",&n,&p);
fo(i,1,n){
ll x;
scanf("%lld",&x);
id[i]=++tot;
x%=(p+1);
key[tot]=x;
}
fo(i,1,n-1){
scanf("%d%d",&j,&k);
add(j,k);add(k,j);
}
dfs(1,0);
tree[root][1]=++tot;
father[tot]=root;
update(root);
splay(tot,0);
root=tot;
scanf("%d",&m);
mask=0;
while (m--){
scanf("%d",&t);
if (t==1){
scanf("%d",&j);
//j^=mask;
j=id[j];
splay(j,0);
root=j;
k=find(tree[j][1],d[j]);
splay(k,0);
root=k;
splay(j,k);
if (d[j]%2==0) t=num[tree[j][1]];else t=sum[tree[j][1]]^num[tree[j][1]];
if (t) printf("MeiZ\n"),mask++;else printf("GTY\n");
}
else if (t==2){
scanf("%d",&j);
//j^=mask;
j=id[j];
ll x;
scanf("%lld",&x);
//x^=mask;
x%=(p+1);
splay(j,0);
root=j;
key[j]=x;
update(j);
}
else{
scanf("%d%d",&j,&k);
//j^=mask;k^=mask;
j=id[j];
id[k]=++tot;
k=id[k];
d[k]=d[j]+1;
ll x;
scanf("%lld",&x);
//x^=mask;
x%=(p+1);
key[k]=x;
splay(j,0);
root=j;
l=tree[j][1];
tree[j][1]=k;
father[k]=j;
tree[k][1]=l;
if (l) father[l]=k;
update(k);
update(j);
/*splay(k,0); root=k;*/
}
}
}
实际上这道题我们是可以树上分块的,然后每块的维护信息相当于上文提到的spaly方法所要维护的信息。
为什么要提分块?别忘了这是gty系列!