设
S
G
[
u
]
SG[u]
SG[u] 表示当前存在的点只剩下
u
u
u 的子树时的状态的
S
G
SG
SG 值
显然整个游戏的
S
G
SG
SG 值为
X
O
R
u
是
根
节
点
S
G
[
u
]
XOR_{u是根节点}SG[u]
XORu是根节点SG[u]
如果上式非零则先手必胜,否则先手必败
考虑通过 DP 求得
S
G
[
u
]
SG[u]
SG[u] 。一个显然的转移方程
S
G
[
u
]
=
m
e
x
{
⨁
w
的
父
亲
在
u
到
v
的
路
径
上
,
w
不
在
u
到
v
的
路
径
上
S
G
[
w
]
,
v
在
u
的
子
树
内
}
SG[u]=mex\{\bigoplus_{w的父亲在u到v的路径上,w不在u到v的路径上} SG[w],v在u的子树内\}
SG[u]=mex{w的父亲在u到v的路径上,w不在u到v的路径上⨁SG[w],v在u的子树内}
(
⨁
\bigoplus
⨁ 为异或运算)
这样的 DP 是
O
(
n
2
)
O(n^2)
O(n2) 的,瓶颈在于枚举
v
v
v
我们设
a
u
=
⨁
v
是
u
的
子
节
点
S
G
[
v
]
a_u=\bigoplus_{v是u的子节点} SG[v]
au=⨁v是u的子节点SG[v] ,
b
u
=
⨁
v
是
u
的
子
节
点
或
v
=
u
S
G
[
v
]
b_u=\bigoplus_{v是u的子节点或v=u}SG[v]
bu=⨁v是u的子节点或v=uSG[v]
那么我们就可以把
⨁
w
的
父
亲
在
u
到
v
的
路
径
上
,
w
不
在
u
到
v
的
路
径
上
S
G
[
w
]
\bigoplus_{w的父亲在u到v的路径上,w不在u到v的路径上}SG[w]
⨁w的父亲在u到v的路径上,w不在u到v的路径上SG[w] 进行一些转化,根据异或运算的消去律,我们有
⨁
w
的
父
亲
在
u
到
v
的
路
径
上
,
w
不
在
u
到
v
的
路
径
上
S
G
[
w
]
=
(
⨁
w
在
u
到
v
的
路
径
上
且
不
为
u
b
w
)
⨁
a
u
\bigoplus_{w的父亲在u到v的路径上,w不在u到v的路径上}SG[w]=(\bigoplus_{w在u到v的路径上且不为u}b_w)\bigoplus a_u
w的父亲在u到v的路径上,w不在u到v的路径上⨁SG[w]=(w在u到v的路径上且不为u⨁bw)⨁au
假设有一棵二进制意义下的 Trie 树,其中储存了所有
u
u
u 的子树内的
v
v
v 对应的
⨁
w
在
u
到
v
的
路
径
上
且
不
为
u
b
w
\bigoplus_{w在u到v的路径上且不为u}b_w
⨁w在u到v的路径上且不为ubw(如果
v
=
u
v=u
v=u 则该式为
0
0
0 ),考虑如何求
S
G
[
u
]
SG[u]
SG[u]
易得我们需要求得一个最小的数
r
e
s
res
res ,使得
a
u
a_u
au 异或上 Trie 树储存的任意一个数都不能得到res
考虑在 Trie 树上从上往下贪心(从高到低确定
r
e
s
res
res 的每一位):设当前要确定第
i
i
i 位(这里二进制位由低往高,最低位为第
0
0
0 位),现在走到 Trie 树的节点
x
x
x
当
a
u
a_u
au 的第
i
i
i 位为
1
1
1 的时候,我们考虑
r
e
s
res
res 的第
i
i
i 位可以为
0
0
0 的条件:
x
x
x 的右子节点(由字符为
1
1
1 的边转移到的子节点)对应的子树不是满二叉树,即子树内的叶子数小于
2
i
2^i
2i (可以在 Trie 的每个节点上储存一个
s
i
z
e
size
size 表示子树内的叶子个数)
这时候我们就可以把
r
e
s
res
res 的第
i
i
i 位设为
0
0
0 ,否则设为
1
1
1 。
a
u
a_u
au 的第
i
i
i 位为
0
0
0 同理
当
x
x
x 到达了叶子节点之后就得到了
S
G
[
u
]
=
r
e
s
SG[u]=res
SG[u]=res
但我们显然不能直接把这棵 Trie 建出来,否则这个 DP 不能得到复杂度上的优化
下面我们设
s
(
u
,
v
)
=
⨁
w
在
u
到
v
的
路
径
上
b
w
s(u,v)=\bigoplus_{w在u到v的路径上}b_w
s(u,v)=⨁w在u到v的路径上bw (其中
u
u
u 是
v
v
v 的祖先),并且
u
u
u 的所有子节点
v
v
v 都有一棵 Trie 树,储存
v
v
v 的子树内所有节点
w
w
w 的
s
(
v
,
w
)
s(v,w)
s(v,w) 值
这时候我们发现:只要把
u
u
u 的所有子节点的 Trie 树合并起来(即对应的所有值合并),再把
0
0
0 插入 Trie 树,我们就得到了我们按位贪心求
S
G
[
u
]
SG[u]
SG[u] 需要用到的 Trie 树
把
u
u
u 的所有子节点的 Trie 树合并起来,可以使用和线段树合并一样的做法,不断递归合并左子树和右子树,到达叶子节点或空节点时停止递归。注意
s
i
z
e
size
size 的更新
已经求得了
S
G
[
u
]
SG[u]
SG[u] 之后,我们就能求得
b
u
b_u
bu 。这时候我们需要让这棵 Trie 里面储存的是
u
u
u 的子树内所有节点
v
v
v 的
s
(
u
,
v
)
s(u,v)
s(u,v) 值(因为计算祖先的
S
G
SG
SG 时还要用到)
这相当于 Trie 树内储存的所有数都异或上
b
u
b_u
bu
我们可以借鉴线段树打标记的思想,在 Trie 树的根节点打上标记
当 Trie 树中插入新数、合并、按位贪心时需要下放标记
下放标记的具体方法:如果要下放 Trie 树节点
x
x
x 上的标记
t
a
g
tag
tag ,且连接
x
x
x 与
x
x
x 的子节点的转移边表示第
i
i
i 位,则把
x
x
x 的左子节点的标记和右子节点的标记都异或上
t
a
g
tag
tag
如果
t
a
g
tag
tag 的第
i
i
i 位为
1
1
1 则需要交换
x
x
x 的左右子节点(因为这时异或上
t
a
g
tag
tag 相当于第
i
i
i 位被取反),最后将
x
x
x 节点上的标记清空
复杂度
O
(
T
n
log
n
)
O(Tn\logn)
O(Tnlogn)
Code
#include<cmath>#include<cstdio>#include<cstring>#include<iostream>#include<algorithm>inlineintread(){int res =0;bool bo =0;char c;while(((c =getchar())<'0'|| c >'9')&& c !='-');if(c =='-') bo =1;else res = c -48;while((c =getchar())>='0'&& c <='9')
res =(res <<3)+(res <<1)+(c -48);return bo ?~res +1: res;}template<classT>inlinevoidSwap(T &a, T &b){T t = a; a = b; b = t;}constint N =1e5+5, M = N <<1, L =7e6+5;int n, m, ecnt, nxt[M], adj[N], go[M], ToT, SG[N], rt[N];bool vis[N];struct Trie
{int lc, rc, tag, sze;voidinit(){
lc = rc = tag = sze =0;}} T[L];voidadd_edge(int u,int v){
nxt[++ecnt]= adj[u]; adj[u]= ecnt; go[ecnt]= v;
nxt[++ecnt]= adj[v]; adj[v]= ecnt; go[ecnt]= u;}voiddowndate(int x,int i){if((T[x].tag >> i)&1)Swap(T[x].lc, T[x].rc);if(T[x].lc) T[T[x].lc].tag ^= T[x].tag;if(T[x].rc) T[T[x].rc].tag ^= T[x].tag;}voidins(int i,int&x,int num){if(!x) T[x =++ToT].init();if(i ==-1)return(void)(T[x].sze =1);downdate(x, i);if((num >> i)&1)ins(i -1, T[x].rc, num);elseins(i -1, T[x].lc, num);
T[x].sze = T[T[x].lc].sze + T[T[x].rc].sze;}intmin_mex_xor(int x,int num){int res =0;for(int i =30; i >=0; i--){downdate(x, i);if((num >> i)&1){if(T[T[x].rc].sze <(1<< i)) x = T[x].rc;else x = T[x].lc, res |=1<< i;}else{if(T[T[x].lc].sze <(1<< i)) x = T[x].lc;else x = T[x].rc, res |=1<< i;}}return res;}intmerge(int i,int x,int y){if(!x ||!y)return x + y;if(i ==-1)return x;downdate(x, i);downdate(y, i);
T[x].lc =merge(i -1, T[x].lc, T[y].lc);
T[x].rc =merge(i -1, T[x].rc, T[y].rc);return T[x].sze = T[T[x].lc].sze + T[T[x].rc].sze, x;}voiddfs(int u,int fu){
vis[u]=1;
rt[u]=0;ins(30, rt[u],0);int x =0;for(int e = adj[u], v; e; e = nxt[e]){if((v = go[e])== fu)continue;dfs(v, u);merge(30, rt[u], rt[v]);
x ^= SG[v];}
SG[u]=min_mex_xor(rt[u], x);
T[rt[u]].tag ^= x ^ SG[u];}voidwork(){int x, y, res =0;
n =read(); m =read();
ecnt =0;for(int i =1; i <= n; i++) adj[i]=0, vis[i]=0;while(m--) x =read(), y =read(),add_edge(x, y);
ToT =0;for(int i =1; i <= n; i++)if(!vis[i])
res ^=(dfs(i,0), SG[i]);puts(res ?"Alice":"Bob");}intmain(){int T =read();while(T--)work();return0;}