BZOJ4730 Alice和Bob又在玩游戏

传送门

首先可以看出这是一道博弈论的题,我们考虑对于每一棵树求出sg。

存在这样两种情况:

1.去掉根,那么这棵树的sg是其所有点的sg异或和。

2.去掉子树中的一个点,那么这棵树的sg是其所有点的sg异或和再异或上这个点到根这条路径上的所有点sg。

对于第一种情况很好处理,而第二种情况暴力做法则是枚举每个点,每次对应的sg不同。

证明可见这篇博客

考虑优化:trie树合并。

把sg转化为二进制数,方便找到mex。

对于当前这棵树

考虑自底向上推出sg值,用trie维护当前子树中,删去一条从根开始的链后得到的每种情况的sg值(即为与这条链相邻的子树的sg值的异或和)构成的集合,于是可以查询mex,通过trie的合并可以构建出当前点的父亲对应的集合,另外要通过打标记实现整棵trie异或上一个值(也就是这条链的异或和)。

#include<bits/stdc++.h>
#define M 100003
#define N 4000003
using namespace std;
int read()
{
    int x=0,f=1;char s=getchar();
    while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
    while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
    return x*f;  
}
struct EDGE{
    int nextt,to;
}w[M*2];
int tot=0,ndnum;
int siz[N],head[M],tag[N],lc[N],rc[N],vis[M],sg[N],rt[N];
void init()
{
    tot=0,ndnum=0;
    memset(siz,0,sizeof(siz));
    memset(w,0,sizeof(w));
    memset(tag,0,sizeof(tag));
    memset(head,0,sizeof(head));
    memset(vis,0,sizeof(vis));
    memset(sg,0,sizeof(sg));
    memset(rt,0,sizeof(rt));
    memset(lc,0,sizeof(lc));
    memset(rc,0,sizeof(rc));
}
void add(int a,int b)
{
    tot++;
    w[tot].nextt=head[a];
    w[tot].to=b;
    head[a]=tot;
}
void pushdown(int k,int d)//d是二进制的位数 
{
    if(!tag[k])return;
    if(tag[k]&(1<<d))swap(lc[k],rc[k]);//建的是trie,^相当于翻转,和tag的这一位同为1才会变化 
    tag[lc[k]]^=tag[k];tag[rc[k]]^=tag[k];
    tag[k]=0;
}
int merge(int x,int y,int d)
{
    if(!x)return y;
    if(!y)return x;
    pushdown(x,d);pushdown(y,d);
    lc[x]=merge(lc[x],lc[y],d-1);
    rc[x]=merge(rc[x],rc[y],d-1);
    if(lc[x]||rc[x])//!!!
      siz[x]=siz[lc[x]]+siz[rc[x]];
    return x;
}
void insert(int &k,int x,int d)
{
    if(!k)
    {
        k=++ndnum;siz[k]=1;
        tag[k]=lc[k]=rc[k]=0;
    }
    if(d==-1)return;
    pushdown(k,d);
    insert((((1<<d)&x)?rc[k]:lc[k]),x,d-1);
    siz[k]=siz[lc[k]]+siz[rc[k]];
}
int query(int k,int d)
{
    if(d==-1)return 0;
    pushdown(k,d);
    if(siz[lc[k]]<(1<<d))//说明有找到mex的机会(可以选到0) 
      return query(lc[k],d-1);
    else return query(rc[k],d-1)|(1<<d);//说明这一位必须选一了 
}
 
void dfs(int x,int fa)
{
    vis[x]=1;
    int res=0;
    for(int i=head[x];i;i=w[i].nextt)
    {
        int v=w[i].to;
        if(v==fa)continue;
        dfs(v,x);
        res^=sg[v];////res当前子树的sg 
    }
    //删除子树里的节点就相当于是子树里这种对应的情况再异或上外边子树的sg。但是我们不可能用一般的方法来存一棵子树里所有的sg
    //处理子树之后,把它合并上来,就能得到当前节点的所有拓展局面的sg了
    for(int i=head[x];i;i=w[i].nextt)//不同的情况合并进去 
    {
        int v=w[i].to;
        if(v==fa)continue;
        tag[rt[v]]^=res^sg[v];
        rt[x]=merge(rt[v],rt[x],20);
    }
    insert(rt[x],res,20);
    sg[x]=query(rt[x],20);//在dfs中处理出了sg值 
}
int main()
{
    int T=read();
    while(T--)
    {
        init();
        int n=read(),m=read();
        for(int i=1;i<=m;++i)
        {
            int a=read(),b=read();
            add(a,b);add(b,a);
        }
        int ans=0;
        for(int i=1;i<=n;++i)
          if(!vis[i])ndnum=0,dfs(i,0),ans^=sg[i];
        if(ans)printf("Alice\n");
        else printf("Bob\n");
    }
}
View Code

 

posted @ 2019-08-13 17:51  yyys  阅读(211)  评论(0编辑  收藏  举报