[bzoj3729]Gty的游戏

10 篇文章 0 订阅
4 篇文章 0 订阅

Description

给定一棵n个节点的数,第i个节点上有ai个石子。
有两个人在van游戏,每次操作者可以从x的子树中的任意一个点选择不超过m个石子移动到它的父亲。
给定t次操作,每次操作询问为x的子树中做游戏先手是否必胜,或者修改一个点的石子数量,又或者是给x新增一个儿子y,石子个数为z。
n,t<=50000,保证任何时刻树中结点个数和编号不超过50000

Solution

如果你看到了这里,先别急着往下看。
因为出题人有毒!!!
说好的结点个数不超过50000呢?

好了,现在我们来讲讲正解。
首先你得会经典NIM游戏。
我们先来分析一下sg函数值。
sg[x]=mexmin(x,m)i=1(sg[xi])sg[0]=0
归纳可得 sg[x]=xmod(m+1)
然后,我们可以发现这是个阶梯NIM游戏。
简单点讲,我们只需要关注奇数层的异或和。(根节点的深度为0)
为什么呢?因为你移动偶数层,别人就可以立马把它移到奇数层。
所以移动偶数层等于没动。

然后就是要处理子树问题了。
动态加点可以用LCT,但子树处理似乎要用Euler Tour Tree…..
本蒟蒻不会写。。。
那么我们发现每次加入的点都是新的(似乎直接插入一棵子树也不是问题)
那么我们可以用splay来维护dfs序。
由于序列是动态的,所以我们需要动态维护每个点的size。
不过简单点想,一个节点所掌管的区间就是这个节点为左端点,节点右边第一个深度小于等于它自己的那个点为右端点。
于是我们可以维护区间min_dep值。
然后维护深度为奇数和偶数的层数的异或和。(奇数和总和我不拦着你)
注意这道题要在右边插入一个虚拟节点,但不用在左边插。
还有,插入没必要搞得那么麻烦,只插入一个点的话,直接插就好啦。

a_crazy_czy那里学来的姿势,这道题可以用定期重构来做。
似乎还可以用树分块(Gty系列)。

Code

#include<cstdio>
#include<cstring>
#include<algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define rep(i,a) for(int i=last[a];i;i=next[i])
#define N 100005
#define Null N-1
using namespace std;
const int inf=0x7fffffff;
int t[N][2],f[N],size[N],sg[N],sg_0[N],sg_1[N],dep[N];
int d[N],n,m,q,x,y,z,l,tot,cnt,ans,bz,root;
int last[N],to[N*2],next[N*2];
void add(int x,int y) {
    to[++l]=y;next[l]=last[x];last[x]=l;
}
void updata(int x) {
    int l=t[x][0],r=t[x][1];
    dep[x]=min(d[x],min(dep[l],dep[r]));
    size[x]=size[l]+size[r]+1;
    sg_0[x]=sg_0[l]^sg_0[r];
    sg_1[x]=sg_1[l]^sg_1[r];
    if (d[x]&1) sg_1[x]^=sg[x];
    else sg_0[x]^=sg[x];
}
int son(int x) {return t[f[x]][1]==x;}
void rotate(int x) {
    int y=f[x],z=son(x);f[x]=f[y];
    if (f[x]) t[f[x]][son(y)]=x;
    if (t[x][1-z]) f[t[x][1-z]]=y;
    f[y]=x;t[y][z]=t[x][1-z];t[x][1-z]=y;
    updata(y);updata(x);
}
void splay(int x,int y) {
    while (f[x]!=y) {
        if (f[f[x]]!=y) 
            if (son(x)==son(f[x])) rotate(f[x]);
            else rotate(x);
        rotate(x);
    }
    if (!y) root=x;
}
void dfs(int x,int y) {
    if (y) d[x]=d[y]+1;
    if (root) f[x]=root,t[root][1]=x;
    splay(x,0);
    rep(i,x) if (to[i]!=y) dfs(to[i],x);
}
int get(int x,int y) {
    if (dep[t[x][0]]<=y) return get(t[x][0],y);
    else if (d[x]<=y) return x;
    else return get(t[x][1],y);
}
int main() {
    scanf("%d%d",&n,&m);d[0]=dep[0]=inf;
    fo(i,1,n) scanf("%d",&sg[i]),sg[i]%=(m+1);
    fo(i,1,n-1) scanf("%d%d",&x,&y),add(x,y),add(y,x);
    d[1]=1;dfs(1,0);f[Null]=root;t[root][1]=Null;splay(Null,0);
    for(scanf("%d",&q);q;q--) {
        scanf("%d",&bz);
        if (bz==1) {
            scanf("%d",&x);x^=cnt;
            splay(x,0);int y=get(t[x][1],d[x]);
            splay(y,x);
            if (d[x]&1) ans=sg_0[t[y][0]];
            else ans=sg_1[t[y][0]];
            if (ans) printf("MeiZ\n"),cnt++;
            else printf("GTY\n");
        } else if (bz==2) {
            scanf("%d%d",&x,&y);x^=cnt;y^=cnt;
            splay(x,0);sg[x]=y%(m+1);updata(x);
        } else {
            scanf("%d%d%d",&x,&y,&z);x^=cnt;y^=cnt;z^=cnt;
            d[y]=d[x]+1;sg[y]=z%(m+1);
            splay(x,0);f[t[x][1]]=y;f[y]=x;
            t[y][1]=t[x][1];t[x][1]=y;
            updata(t[y][1]);updata(y);updata(x);
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值