[BZOJ3729]Gty的游戏(博弈论+Splay)

=== ===

这里放传送门

=== ===

题解

首先看这道题的博弈部分,也就是如何得到答案。对于单个节点来说,这就是一个简单的巴什博弈问题,每次可以拿走[1..L]个的话,只需要把石子个数对L+1取模就可以得到它的SG值了。如果考虑树上操作这就是一个阶梯博弈,每次只需要考虑奇数层上的石子。那么只需要对每棵子树维护它的奇数层SG值和偶数层SG值就可以了。因为有插入节点的操作所以dfs序要用Splay来维护。

这道题里面最关键的操作是插入节点的操作。因为访问子树的时候要知道它的入栈点和出栈点,也就是in和out,而如果插入了一个节点就有可能引起很多节点out值的变化。

一个节点的in显然就是它自己,但out值是它子树里最后一个被遍历的节点,也就是它一定是一个叶子节点。那么如果在某个叶子节点下面接了一个节点,就有至少一个节点的out会变化。而我们可以发现这个寻找out的关系是具有传递性的,比如树上某个节点x把u当做它的out,而u当前的out节点指向新接入的儿子v,那么x的out也应该指向v。这就启示我们可以用并查集来维护这个东西,把每个节点并到它的out上去就可以了。

而为了让修改out的次数尽量少,如果接入新节点的那个父亲不是叶子节点,我们可以通过适当处理让所有节点的out都不变,方法就是把新节点的dfs序紧挨着父节点放在它后面。比如当前有dfs序:u-v-w,v和w都是u的儿子,那么显然u的out是w。如果现在还要给u接一个新儿子x,那么我们可以把dfs序修改成:u-x-v-w,这样的话所有已有节点的in和out都不会变。而如果修改成:u-v-w-x,u的out就变了,就造成了不必要的修改。

代码

#include<map>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n,L,m,tot,v[100010],p[100010],a[200010],nxt[200010],lastans,deep[100010],num[100010],cnt;
map<int,int>hash;
struct Node{
    Node *ch[2],*fa;
    int Osum,Esum,pos,val;
    Node();
    void count();
    bool pl(){return this==fa->ch[1];}
    Node* Pre();
    Node* Nxt();
}*null,*Root,*in[100010],*out[100010],P[100010];
Node::Node(){ch[0]=ch[1]=fa=null;Osum=Esum=pos=val=0;}
void Node::count(){
    Osum=ch[0]->Osum^ch[1]->Osum;
    Esum=ch[0]->Esum^ch[1]->Esum;
    if (deep[pos]&1) Osum^=val;
    else Esum^=val;
}
Node* Node::Pre(){
    Node *k=this;
    if (ch[0]!=null){
        k=ch[0];
        while (k->ch[1]!=null) k=k->ch[1];
        return k;
    }
    while (k==k->fa->ch[0]) k=k->fa;
    return k->fa;
}
Node* Node::Nxt(){
    Node *k=this;
    if (ch[1]!=null){
        k=ch[1];
        while (k->ch[0]!=null) k=k->ch[0];
        return k;
    }
    while (k==k->fa->ch[1]) k=k->fa;
    return k->fa;
}
void add(int x,int y){
    tot++;a[tot]=y;nxt[tot]=p[x];p[x]=tot;
}
void Rotate(Node *k){
    Node *r=k->fa;
    if (r==null||k==null) return;
    int x=k->pl()^1;
    r->ch[x^1]=k->ch[x];
    if (k->ch[x]!=null)
      r->ch[x^1]->fa=r;
    if (r->fa!=null)
      r->fa->ch[r->pl()]=k;
    else Root=k;
    k->fa=r->fa;
    r->fa=k;
    k->ch[x]=r;
    r->count();k->count();
}
void Splay(Node *r,Node *tar){
    for (;r->fa!=tar;Rotate(r))
      if (r->fa->fa!=tar)
        Rotate(r->pl()==r->fa->pl()?r->fa:r);
}
Node* Find_out(Node *x){
    if (out[x->pos]==x) return out[x->pos];
    out[x->pos]=Find_out(out[x->pos]);
    return out[x->pos];
}
int Getans(Node *x){
    Node *tmp=in[x->pos];
    int num;
    Splay(tmp->Pre(),null);
    tmp=Find_out(x);
    Splay(tmp->Nxt(),Root);
    num=x->pos;
    if (deep[num]&1) return Root->ch[1]->ch[0]->Esum;
    else return Root->ch[1]->ch[0]->Osum;
}
void dfs(int u,int fa){
    deep[u]=deep[fa]+1;
    num[++cnt]=u;
    in[u]=P+u;
    for (int i=p[u];i!=0;i=nxt[i])
      if (a[i]!=fa) dfs(a[i],u);
    out[u]=P+num[cnt];
}
Node *build(int l,int r){
    if (l>r) return null;
    int mid=(l+r)>>1;
    Node *k=P+num[mid];
    if (mid==0||mid==n+1)
      k=new Node();
    k->ch[0]=build(l,mid-1);
    k->ch[1]=build(mid+1,r);
    if (k->ch[0]!=null) k->ch[0]->fa=k;
    if (k->ch[1]!=null) k->ch[1]->fa=k;
    k->count();return k;
}
int main()
{
    null=new Node;*null=Node();
    scanf("%d%d",&n,&L);
    for (int i=1;i<=n;i++){
        int x;scanf("%d",&x);
        x%=(L+1);v[i]=x;
        P[i]=Node();hash[i]=i;
        P[i].pos=i;P[i].val=v[i];
    }
    for (int i=1;i<n;i++){
        int x,y;scanf("%d%d",&x,&y);
        add(x,y);add(y,x);
    }
    dfs(1,0);
    Root=build(0,n+1);
    scanf("%d",&m);
    for (int i=1;i<=m;i++){
        int type;scanf("%d",&type);
        switch (type){
            case 1:{
                int v;scanf("%d",&v);
                v^=lastans;
                v=hash[v];
                if (Getans(P+v)==0) printf("GTY\n");
                else{lastans++;printf("MeiZ\n");}
                break;
            }
            case 2:{
                int x,y;scanf("%d%d",&x,&y);
                x^=lastans;y^=lastans;
                x=hash[x];y%=(L+1);
                Splay(in[x]->Pre(),null);
                Splay(in[x]->Nxt(),Root);
                Root->ch[1]->ch[0]->val=y;
                Root->ch[1]->ch[0]->count();
                Root->ch[1]->count();
                Root->count();break;
            }
            case 3:{
                int u,v,x;
                Node *now;
                scanf("%d%d%d",&u,&v,&x);
                u^=lastans;v^=lastans;x^=lastans;
                u=hash[u];v=hash[v]=++n;
                P[v]=Node();now=P+v;
                in[v]=out[v]=now;
                deep[v]=deep[u]+1;//修改deep必须在count之前
                now->pos=v;
                now->val=x%(L+1);
                now->count();
                Splay(in[u],null);
                Splay(in[u]->Nxt(),Root);
                Root->ch[1]->ch[0]=now;
                now->fa=Root->ch[1];
                if (out[u]==in[u]) out[u]=now;//如果父节点是叶子就要修改out
                Root->ch[1]->count();
                Root->count();break;
            }
        }
    }
    return 0;
}

偏偏在最后出现的补充说明

用Splay维护dfs序是常见的思路啦。
常见的博弈模型一定要记牢,很多简单的博弈问题都是直接套模型就可以解决的。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值