[bzoj3729]Gty的游戏

本文探讨了一个涉及游戏树策略的问题,具体是在一棵有根树中移动石子的游戏,目标是找出先手是否有必胜策略。文章详细介绍了输入输出格式,并提出了通过计算节点的sg值和使用splay树进行区间维护来解决此问题的方法。

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

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”的个数。

分析

首先考虑如果没有添加操作该怎么做。
由于每次取数的上限是m,至少要去一次,对于一个节点,如果它有x个石子,sg值就为x%(m+1)。(这个脑补一下就好了)
然后每次把一个节点的石子移动到它的父亲,也就是移到深度比它小1的节点。容易发现这是个阶梯nim,所以对于一个询问x,只把它的儿子中深度的奇偶性和它不一样的节点的sg值异或在一起。
那么先跑出整棵树的dfn,然后是区间维护两个异或和。

现在加上添加操作。
每次给u添加一个儿子v,考虑在dfs序里找到一个区间[l,r],表示以u为根的子树,然后在区间后面加上节点v。所以可以考虑用splay。在dfs序里,这个区间[l,r]中的l就是节点u,要确定区间的r在哪里,就是在l后面找到第一个深度不大于u的深度的节点。

时间复杂度O(nlogn)

/**************************************************************
    Problem: 3729
    User: worldwide
    Language: C++
    Result: Accepted
    Time:816 ms
    Memory:9340 kb
****************************************************************/

#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

const int maxn=100005,Null=maxn-1;

int root,fa[maxn],son[maxn][2],dep[maxn],min_dep[maxn],sg[maxn],xorsum_0[maxn],xorsum_1[maxn],pos[maxn],pre[maxn];

int h[maxn],e[maxn],next[maxn],tot,cnt,a[maxn],n,m,t;

void init(int x,int f,int d)
{
    pos[x]=++tot;
    dep[tot]=d;
    sg[tot]=a[x]%(m+1);
    for (int i=h[x];i;i=next[i]) if (e[i]!=f) init(e[i],x,d+1);
}

void add(int x,int y)
{
    e[++tot]=y; next[tot]=h[x]; h[x]=tot;
}

void update(int x)
{
    min_dep[x]=min(dep[x],min(min_dep[son[x][0]],min_dep[son[x][1]]));
    xorsum_0[x]=xorsum_0[son[x][0]]^xorsum_0[son[x][1]];
    xorsum_1[x]=xorsum_1[son[x][0]]^xorsum_1[son[x][1]];
    if (dep[x]&1) xorsum_1[x]^=sg[x];else xorsum_0[x]^=sg[x];
}

void Rotate(int x,int t)
{
    int y=fa[x];
    if (fa[y]!=Null)
    {
        if (son[fa[y]][0]==y) son[fa[y]][0]=x;else son[fa[y]][1]=x;
    }else root=x;
    fa[x]=fa[y]; fa[y]=x;
    son[y][t]=son[x][t^1]; if (son[y][t]!=Null) fa[son[y][t]]=y;
    son[x][t^1]=y;
    update(y);
}

void splay(int x,int f)
{
    while (fa[x]!=f)
    {
        int y=fa[x];
        if (fa[y]==f)
        {
            if (son[y][0]==x) Rotate(x,0); else Rotate(x,1);
        }else
        {
            int z=fa[y];
            if (son[z][0]==y)
            {
                if (son[y][0]==x)
                {
                    Rotate(y,0); Rotate(x,0);
                }else
                {
                    Rotate(x,1); Rotate(x,0);
                }
            }else
            {
                if (son[y][0]==x)
                {
                    Rotate(x,0); Rotate(x,1);
                }else
                {
                    Rotate(y,1); Rotate(x,1);
                }
            }
        }
    }
    update(x);
}

int get(int x,int d)
{
    while (1)
    {
        if (min_dep[son[x][0]]<=d) x=son[x][0];
        else if (dep[x]<=d) return x;
        else x=son[x][1];
    }
}

void work()
{
    int typ,x,u,v;
    scanf("%d",&typ);
    if (typ==1)
    {
        scanf("%d",&x);
        x=pos[x^cnt];
        splay(x,Null);
        splay(get(son[x][1],dep[x]),root);
        if (dep[x]&1) u=xorsum_0[son[son[x][1]][0]];else u=xorsum_1[son[son[x][1]][0]];
        if (u)
        {
            printf("MeiZ\n"); cnt++;
        }else printf("GTY\n");
    }else if (typ==2)
    {
        scanf("%d%d",&u,&v);
        u=pos[u^cnt];
        sg[u]=(v^cnt)%(m+1);
        splay(u,Null);
    }else
    {
        scanf("%d%d%d",&u,&v,&x);
        u=pos[u^cnt];
        splay(u,Null);
        int t=get(son[u][1],dep[u]);
        splay(pre[t],Null);
        splay(t,root);
        v^=cnt;
        pos[v]=++tot;
        fa[tot]=son[root][1];
        son[son[root][1]][0]=tot;
        dep[tot]=dep[u]+1;
        sg[tot]=(x^cnt)%(m+1);
        pre[tot]=pre[fa[tot]];
        pre[fa[tot]]=tot;
        son[tot][0]=son[tot][1]=Null;
        splay(tot,root);
    }
}

int main()
{
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;i++) scanf("%d",&a[i]);
    for (int i=1;i<n;i++)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        add(x,y); add(y,x);
    }
    tot=0;
    init(1,0,1);
    for (int i=n+1;i>=0;i--)
    {
        fa[i]=i-1;
        pre[i]=i-1;
        if (i<=n) son[i][1]=i+1;else son[i][1]=Null;
        son[i][0]=Null;
        xorsum_0[i]=xorsum_0[i+1];
        xorsum_1[i]=xorsum_1[i+1];
        if (dep[i]&1) xorsum_1[i]^=sg[i];else xorsum_0[i]^=sg[i];
    }
    fa[0]=Null; dep[Null]=min_dep[Null]=maxn;
    for (tot++,scanf("%d",&t);t--;work());
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值