[BZOJ]4730: Alice和Bob又在玩游戏 sg函数+trie

Description

Alice和Bob在玩游戏。有n个节点,m条边(0<=m<=n-1),构成若干棵有根树,每棵树的根节点是该连通块内编号最小的点。Alice和Bob轮流操作,每回合选择一个没有被删除的节点x,将x及其所有祖先全部删除,不能操作的人输。注:树的形态是在一开始就确定好的,删除节点不会影响剩余节点父亲和儿子的关系。比如:1-3-2 这样一条链,1号点是根节点,删除1号点之后,3号点还是2号点的父节点。问有没有先手必胜策略。n<=10^5。

Solution

考虑暴力求 s g sg sg,枚举删掉哪个点,此时的 s g sg sg就是剩下的子树 s g sg sg的异或和。
考虑从下往上求出每棵子树的 s g sg sg,如果去掉根节点,那么 s g sg sg就是它的所有儿子子树的 s g sg sg异或和;否则如果去掉的点在某棵子树内,那么得到的 s g sg sg就是在这棵子树内去掉该点得到的 s g sg sg再异或上外面其它子树的异或和。
因此考虑每个节点用一个trie维护子树 s g sg sg值的集合,那么需要支持的操作就是给一个集合异或上一个数,可以用打标记实现,如果对应的位置上是 1 1 1就是交换左右儿子;还有集合的合并,类似线段树合并就行了;求 mex \text {mex} mex值,可以在trie上二分。

Code

#include<bits/stdc++.h>
using namespace std;
#define LL long long
#define pa pair<int,int>
const int Maxn=100010;
const int inf=2147483647;
int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
    return x*f;
}
int n,m,sg[Maxn],root[Maxn],son[Maxn*20][2],tot=0,tag[Maxn*20],sz[Maxn*20];
struct Edge{int y,next;}e[Maxn<<1];
int last[Maxn],len=0;
void ins(int x,int y)
{
    int t=++len;
    e[t].y=y;e[t].next=last[x];last[x]=t;
}
void up(int x){sz[x]=sz[son[x][0]]+sz[son[x][1]]+1;}
void down(int x,int d)
{
    if(!tag[x])return;
    if((1<<d)&tag[x])swap(son[x][0],son[x][1]);
    int lc=son[x][0],rc=son[x][1];
    if(lc)tag[lc]^=tag[x];
    if(rc)tag[rc]^=tag[x];
    tag[x]=0;
}
void merge(int&u1,int u2,int d)
{
    if(!u1){u1=u2;return;}
    if(!u2||d<0)return;
    down(u1,d),down(u2,d);
    merge(son[u1][0],son[u2][0],d-1);
    merge(son[u1][1],son[u2][1],d-1);
    up(u1);
}
int tmp[20],lt;
void insert(int&u,int x)
{
    lt=0;
    if(!u)u=++tot;int cur=u;
    for(int i=17;i>=0;i--)
    {
        int t=(x>>i)&1;tmp[++lt]=cur;
        if(!son[cur][t])sz[son[cur][t]=++tot]=1;
        cur=son[cur][t];
    }
    for(int i=lt;i;i--)up(tmp[i]);
}
int mex(int u,int d)
{
    int re=0;
    while(d>=0)
    {
        if(d)down(u,d);
        int lc=son[u][0],rc=son[u][1];
        if(sz[lc]!=(1<<(d+1))-1)u=lc;
        else re|=(1<<d),u=rc;
        d--;
    }
    return re;
}
bool vis[Maxn];
void dfs(int x,int ff)
{
    int t=0;vis[x]=true;
    for(int i=last[x];i;i=e[i].next)
    {
        int y=e[i].y;
        if(y==ff)continue;
        dfs(y,x);t^=sg[y];
    }
    insert(root[x],t);
    for(int i=last[x];i;i=e[i].next)
    {
        int y=e[i].y;
        if(y==ff)continue;
        tag[root[y]]^=(t^sg[y]);
        merge(root[x],root[y],17);
    }
    sg[x]=mex(root[x],17);
}
int main()
{
    int T=read();
    while(T--)
    {
        for(int i=1;i<=tot;i++)son[i][0]=son[i][1]=tag[i]=sg[i]=sz[i]=0;
        for(int i=1;i<=n;i++)root[i]=0;
        memset(vis,false,sizeof(vis));
        memset(last,0,sizeof(last));len=tot=0;
        n=read(),m=read();
        for(int i=1;i<=m;i++)
        {
            int x=read(),y=read();
            ins(x,y),ins(y,x);
        }
        int ans=0;
        for(int i=1;i<=n;i++)if(!vis[i])dfs(i,0),ans^=sg[i];
        if(ans)puts("Alice");
        else puts("Bob");
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值