4730: Alice和Bob又在玩游戏

4730: Alice和Bob又在玩游戏

Time Limit: 40 Sec   Memory Limit: 1024 MB
Submit: 116   Solved: 52
[ Submit][ Status][ Discuss]

Description

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

Input

Output

Sample Input

4
2 1
1 2
3 2
1 2
1 3
2 0
3 1
1 2

Sample Output

Alice
Alice
Bob
Alice

HINT

Source

[ Submit][ Status][ Discuss]

记sgi为以i为根的子树的sg值,则当所有连通块的sg值都统计好时,就可以用sg定理求解了
每个sg值可以通过暴力O(n)枚举转移统计,但这样复杂度是O(n^2)的
4730: Alice和Bob又在玩游戏_第1张图片
在dfs过程中递归地统计sg值,如果是叶子节点,显然sg值为1
当需要统计x的sg值的时候,x的所有后代的sg值肯定都已经统计好了
假设其中一个转移,是选择了节点y,那么要把链(x,y)上的点全部删除
此时剩下的就是这条链上每个节点的所有儿子,就是图中的三角形了
那么这时候的sg值就是那些三角形的根的sg值的xor和
对于每个点,维护一棵trie,在递归结束的时候与其它儿子的trie一起合并
但是在合并前,trie内的值肯定是要改变的,观察发现,这个改变,就是每个数都xor上一个数字
也就是说,假设x的一个儿子z的trie,要把它的信息给x,
那么z的trie里面的所有数字都要xor上x除了z的其它儿子的sg值xor和
就要实现trie里同时xor上一个数,打个懒惰标记就解决了
具体的,trie每一个节点记录是第几层,记录标记,生效的时候对应就是交换儿子
trie合并就像线段树合并就行了
有了trie,mex操作在trie上二分即可
总复杂度是O(nlog10^9)
#include
#include
using namespace std;
 
const int maxn = 1E5 + 10;
const int T = 20;
const int N = 15;
 
int Case,n,m,cnt,Ans,rt[maxn],ch[maxn*T*N][2],Mark[maxn*T*N],L[maxn*T*N],sg[maxn],sum[maxn];
bool All[maxn*T*N],vis[maxn];
 
vector  v[maxn];
 
void Clear()
{
    for (int i = 1; i <= n; i++)
        v[i].clear(),sum[i] = rt[i] = vis[i] = 0;
    for (int i = 1; i <= cnt; i++)
        ch[i][0] = ch[i][1] = Mark[i] = All[i] = 0;
    cnt = Ans = 0;
}
 
void pushdown(int o)
{
    if (!Mark[o]) return;
    if (Mark[o] & (1 << L[o])) swap(ch[o][0],ch[o][1]);
    for (int i = 0; i < 2; i++)
        if (ch[o][i]) Mark[ch[o][i]] ^= Mark[o];
    Mark[o] = 0;
}
 
int Merge(int o1,int o2)
{
    if (!o1) return o2;
    if (!o2) return o1;
    if (L[o1] == -1) return o1;
    pushdown(o1); pushdown(o2);
    for (int i = 0; i < 2; i++)
        ch[o1][i] = Merge(ch[o1][i],ch[o2][i]);
    if (All[ch[o1][0]] && All[ch[o1][1]]) All[o1] = 1;
    return o1;
}
 
void Insert(int o,int k)
{
    if (L[o] == -1) {All[o] = 1; return;}
    int Nex = k & (1 << L[o]) ? 1 : 0;
    if (!ch[o][Nex]) ch[o][Nex] = ++cnt,L[cnt] = L[o] - 1;
    Insert(ch[o][Nex],k);
    if (All[ch[o][0]] && All[ch[o][1]]) All[o] = 1;
}
 
int Query(int o)
{
    int ret = 0;
    for (int i = T - 1; i >= 0; i--)
    {
        pushdown(o);
        int Nex = All[ch[o][0]] ? 1 : 0;
        ret |= (Nex << i); o = ch[o][Nex];
    }
    return ret;
}
 
void Dfs(int x,int from)
{
    L[rt[x] = ++cnt] = T - 1;
    for (int i = 0; i < v[x].size(); i++)
    {
        int to = v[x][i];
        if (to == from) continue;
        vis[to] = 1; Dfs(to,x); sum[x] ^= sg[to];
    }
    for (int i = 0; i < v[x].size(); i++)
    {
        int to = v[x][i];
        if (to == from) continue;
        Mark[rt[to]] ^= (sum[x] ^ sg[to]);
        rt[x] = Merge(rt[x],rt[to]);
    }
    Insert(rt[x],sum[x]); sg[x] = Query(rt[x]);
}
 
int getint()
{
    char ch = getchar(); int ret = 0;
    while (ch < '0' || '9' < ch) ch = getchar();
    while ('0' <= ch && ch <= '9')
        ret = ret * 10 + ch - '0',ch = getchar();
    return ret;
}
 
void Solve()
{
    n = getint(); m = getint();
    while (m--)
    {
        int x = getint(),y = getint();
        v[x].push_back(y); v[y].push_back(x);
    }
    for (int i = 1; i <= n; i++)
        if (!vis[i]) vis[i] = 1,Dfs(i,0),Ans ^= sg[i];
    puts(Ans ? "Alice" : "Bob"); Clear();
}
 
int main()
{
    #ifdef DMC
        freopen("DMC.txt","r",stdin);
    #endif
     
    Case = getint();
    while (Case--) Solve();
    return 0;
}



你可能感兴趣的:(dfs,树,博弈论,trie)