[BZOJ3729]Gty的游戏/[JZOJ4759]石子游戏

题目大意

一棵树,初始时有 n 个节点,第 i 个节点上有 ai 个石子。给定 m ,有 q 个操作形如一下三种:
修改一个点的石子个数
在一个节点下面添加一个有若干个石子的新点
查询对点 x 为根的子树玩一下的游戏,先手是否必胜:每次选择一个点,将该点上不超过 m 个石子(不能为 0 )移动到父亲节点,不能移动者输。
1n,m,q5×104,ai[0,231)
保证任何时候节点的编号和个数都不超过 5×104


题目分析

NIM的一些姿势

首先是如何解决移动不超过 m 个石子的问题,我们写一下 SG 函数转移式: sg(x)=mex{sg(xi)|0<im} 。我们可以发现一个很显然的规律: sg(x)=x mod (m+1)
接着解决将石子移动到父亲的问题,其实这就是一个阶梯 nim 游戏的模型。将(子树)根节点看成第 0 层,然后我们只需要统计子树中所有奇数层的 nim 和(异或和)即可。为什么呢?因为偶数层的石子相当于“不存在的”,因为如果有一个人将一个石子从偶数层移动到奇数层,那么另一个人一定可以将它从奇数层移动到偶数层。而奇数层的石子一旦移动到偶数层,就相当于“消失了”。

各种乱搞

现在问题变为如何在支持插入和修改的情况下,维护子树的 nim 和(奇偶分两种存就好了)。
一个很显然的想法是使用 DFS 序,但是加入新点无疑是一个棘手的问题。其实我们可以用 splay 维护 DFS 序的区间 nim 和还有最小深度。一个点的子树的最后一个点的位置一定是该点后面第一个深度小于等于它的位置的前一个。随便维护一下就好了。时间复杂度 O(nlog2n)
但是我 splay 不是很熟练,怎么办咧?骑士这题我们还可以定期重构。将所有修改操作先储存下来,如果达到了 q 个再暴力重构整棵树来得到我们想要的信息,查询时候除了在树中统计还要暴力那些存下来的个操作。
具体怎么实现呢?对一棵树,我们求出 DFS 序的前缀 nim 和数组。修改和插入操作我们先把它存下来,注意维护待插入点的树结构。查询的时候,如果查询的是未插入的点(被存下来的点),那我就可以直接暴力整棵子树(显然节点个数小于等于 q )。如果查询的是树上的点,我们就先利用前缀 nim 和数组求出初始答案,然后枚举每一个修改操作,判一判对答案是否有影响,如果有就改一下。再枚举每一个待加入的点,依然判一判对答案是否有影响(看一看待加入点构成的树的根节点的父亲是否在子树内),改一改。具体细节就交给大家想一想吧。当存下来的东西达到 q 个的时候,我们将整棵树重新 DFS 一次,再次处理前缀 nim 和数组即可。
时间复杂度 O(qq)


代码实现

出题人不遵守约定,点数可能超过 5×104 !!!

#include 
#include 
#include 
#include 
#include 
#include 

using namespace std;

int read()
{
    int x=0,f=1;
    char ch=getchar();
    while (!isdigit(ch)) f=ch=='-'?-1:f,ch=getchar();
    while (isdigit(ch)) x=x*10+ch-'0',ch=getchar();
    return x*f;
}

const int N=100050;
const int M=N<<1;

int a[N],fa[N],last[N],DFN[N],size[N],nim[N][2],anc[N],add[N],mod[N][2],deep[N],tc[N];
int n,q,lim,tot,mdcnt,bs,idx,lst,tid;
int next[M],tov[M];

int SG(int x){return x%(lim+1);}

void insert(int x,int y){tov[++tot]=y,next[tot]=last[x],last[x]=tot;}

void dfs(int x)
{
    size[x]=1,DFN[x]=++idx,anc[x]=0;
    nim[DFN[x]][deep[x]&1]=SG(a[x]),nim[DFN[x]][(deep[x]&1)^1]=0;
    for (int i=last[x],y;i;i=next[i])
        if ((y=tov[i])!=fa[x])
            fa[y]=x,deep[y]=deep[x]+1,dfs(y),size[x]+=size[y];
}

void pre(){for (int i=1;i<=idx;i++) nim[i][0]^=nim[i-1][0],nim[i][1]^=nim[i-1][1];}

int brute(int x,int og)
{
    int ret=(((deep[x]&1)!=(deep[og]&1))&&x!=og)?SG(a[x]):0;
    for (int i=last[x],y;i;i=next[i])
        if ((y=tov[i])!=fa[x]) ret^=brute(y,og);
    return ret;
}

bool win(int x)
{
    tid++;
    int sg;
    if (anc[x]) sg=brute(x,x);
    else
    {
        sg=nim[DFN[x]+size[x]-1][(deep[x]&1)^1]^nim[DFN[x]][(deep[x]&1)^1];
        for (int i=1;i<=add[0];i++)
            if (DFN[x]<=DFN[anc[add[i]]]&&DFN[anc[add[i]]]<=DFN[x]+size[x]-1&&((deep[x]&1)!=(deep[add[i]]&1)))
                sg^=SG(a[add[i]]);
        for (int p,i=mdcnt;i;i--)
            if (tc[p=mod[i][0]]!=tid)
            {
                tc[p]=tid;
                if (p!=x&&((deep[x]&1)!=(deep[p]&1))&&(anc[p]&&DFN[x]<=DFN[anc[p]]&&DFN[anc[p]]<=DFN[x]+size[x]-1||!anc[p]&&DFN[x]<=DFN[p]&&DFN[p]<=DFN[x]+size[x]-1)) sg^=SG(a[p])^SG(mod[i][1]);
            }
    }
    return sg;
}

void rebuild()
{
    for (int i=1;i<=mdcnt;i++) a[mod[i][0]]=mod[i][1];
    mdcnt=0,add[0]=0;
    idx=0,deep[1]=1,dfs(1),pre();
}

int main()
{
    freopen("game.in","r",stdin),freopen("game.out","w",stdout);
    n=read(),lim=read();
    for (int i=1;i<=n;i++) a[i]=read();
    for (int i=1,x,y;ix=read(),y=read();
        insert(x,y),insert(y,x);
    }
    idx=0,deep[1]=1,dfs(1),pre();
    q=read(),bs=trunc(sqrt(q))+1,lst=0,tid=0;
    for (int i=1,t,u,v,x;i<=q;i++)
    {
        t=read();
        switch (t)
        {
            case 1:
            {
                x=read()^lst;
                if (win(x)) printf("Yes\n"),lst++;
                else printf("No\n");
                break;
            }
            case 2:
            {
                u=read()^lst,x=read()^lst;
                mod[++mdcnt][0]=u,mod[mdcnt][1]=x;
                break;
            };
            case 3:
            {
                u=read()^lst,v=read()^lst,x=read()^lst;
                fa[v]=u,a[v]=x,deep[v]=deep[u]+1,anc[v]=anc[u]?anc[u]:u,insert(u,v);
                add[++add[0]]=v;
            }
        }
        if (add[0]+mdcnt==bs) rebuild();
    }
    fclose(stdin),fclose(stdout);
    return 0;
}

你可能感兴趣的:(BZOJ,纪中OJ,定期重构,博弈论,Splay)