Alice 和 Bob 又在玩游戏。
有 n 个节点, m 条边( 0≤m≤n−1 ),构成若干棵有根树,每棵树的根节点是该连通块内编号最小的点。
Alice 和 Bob 轮流操作(Alice 先手),每回合选择一个没有被删除的节点 x ,将 x 及其所有祖先全部删除,不能操作的人输。
需要注意的是,树的形态是在一开始就确定好的,删除节点不会影响剩余节点父亲和儿子的关系。
比如:1-3-2 这样一条链,1 号点是根节点,删除 1 号点之后,3 号点还是 2 号点的父节点。
假设 Alice 和 Bob 都足够聪明,问 Alice 有没有必胜策略。
输入格式
第一行一个正整数 T ,表示该测试点有 T 组数据;接下来 T 组数据。
对于每组数据:
输入第一行两个整数 n , m ,分别表示点数和边数(节点从 1 开始编号)。
接下来 m 行,每行两个正整数 a , b ,表示节点 a 和节点 b 之间有一条边,输入数据中没有重边。
输出格式
输出 T 行,每行输出 Alice 先手并且 Alice 和 Bob 都足够聪明的情况下谁获胜。
样例
input
4 2 1 1 2 3 2 1 2 1 3 2 0 3 1 1 2
output
Alice Alice Bob Alice
explanation
输入共 4 组数据;
第一组数据输入是一条链,Alice 可以一次性把所有节点都删掉。
第二组数据,Alice 先手第一步删除 1 号点即可胜利。
限制与约定
对于 10% 的数据, m=0 ;
对于 20% 的数据, 1≤n≤20 ;
对于 40% 的数据, 1≤n≤102 ;
对于 60% 的数据, 1≤n≤103 ;
对于 100% 的数据, 1≤T≤10,1≤n≤105,∑n≤2×105,0≤m≤n−1 ,输入数据保证不会形成环,且每棵树的大小 ≤5×104 。
时间限制: 2s
对于一个子树的根为root,假定x在以root为根的子树中,那么删除x到根的所有节点之后剩下的游戏状态就是son[z](其中z在x到root的路径上),也就是删掉x的后继状态就是son[z]的sg1值的异或和,那么root的sg值就是他的所以子节点的sg1值的mex。(注意区分sg1和sg)
然后我们发现对于每个根节点他的儿子的sg1值都是不断在变化的,对于一个x,他的sg1值为sg1[son[x]],以及sg1[son[root]](其中x不在son[root]为根的子树中),我们用一个trie维护,每个trie维护的就是某个点到以一个特定点为root的sg1值,假如我们对于一个z已经维护了它到x的sg1值,那么它到fa[x]的sg1值就是相当于它的所有sg1值异或上sg1[son[fa[x]]其中son[fa[x]]!=x。如果用trie维护一个点到某个点的sg1值,那么我们就只要异或上一个数就行了,当然还有支持sg1的合并也就是trie的合并。实现中有一些细节需要注意。
#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int MAXN = 100005;
int f[MAXN], i, n, j, m, k, l, x, y, T;
int first[MAXN], next[MAXN << 1], go[MAXN << 1], t, size[MAXN * 21];
int root[MAXN], s[MAXN * 21][2], nn, d[20], len, cnt[MAXN * 21], ans;
bool vis[MAXN];
inline void add(const int &x, const int &y)
{
next[++t] = first[x]; first[x] = t; go[t] = y;
}
inline int get()
{
char c;
while ((c = getchar()) < 48 || c > 57);
int res = c - 48;
while ((c = getchar()) >= 48 && c <= 57)
res = res * 10 + c - 48;
return res;
}
inline void rev(int x, int dep, int y)
{
cnt[x] ^= y;
if ((y >> (nn - dep - 1)) & 1)
swap(s[x][0], s[x][1]);
}
inline void putdown(int x, int dep)
{
if (cnt[x])
{
if (s[x][0]) rev(s[x][0], dep + 1, cnt[x]);
if (s[x][1]) rev(s[x][1], dep + 1, cnt[x]);
cnt[x] = 0;
}
}
inline void insert(int x, int sum)
{
for(int i = 1; i < nn; i ++)
d[i] = ((sum >> (nn - i - 1)) & 1);
size[x] ++;
for(int i = 1; i < nn; i ++)
{
if (!s[x][d[i]]) s[x][d[i]] = ++len;
x = s[x][d[i]];
size[x] ++;
}
}
inline int merge(int x, int y, int dep)
{
if (!x) return y;
if (!y) return x;
putdown(y, dep);
s[x][0] = merge(s[x][0], s[y][0], dep + 1);
s[x][1] = merge(s[x][1], s[y][1], dep + 1);
size[x] = size[s[x][0]] + size[s[x][1]];
if (dep == nn) size[x] = 1;
return x;
}
inline void dfs(int now, int fa)
{
vis[now] = 1; f[now] = 0;
int tot = 0;
for(int i = first[now]; i; i = next[i])
if (go[i] != fa) dfs(go[i], now), tot ^= f[go[i]];
insert(root[now] = ++len, tot);
for(int i = first[now]; i; i = next[i])
if (go[i] != fa)
{
rev(root[go[i]], 1, tot ^ f[go[i]]);
root[now] = merge(root[now], root[go[i]], 1);
}
int x = root[now];
for(int i = 1; i < nn; i ++)
{
if (!x) break;
putdown(x, i);
if (size[s[x][0]] == (1 << (nn - i - 1))) f[now] += (1 << (nn - i - 1)), x = s[x][1];
else x = s[x][0];
}
if (x) f[now] ++;
}
int main()
{
cin >> T;
while (T --)
{
len = t = 0;
cin >> n >> m;
nn = log2(n) + 2;
memset(vis, 0, sizeof(vis));
memset(first, 0, sizeof(first));
memset(s, 0, sizeof(s));
memset(cnt, 0, sizeof(cnt));
memset(size, 0, sizeof(size));
for(i = 1; i <= m; i ++)
{
x = get(); y = get();
add(x, y);
add(y, x);
}
ans = 0;
for(i = 1; i <= n; i ++)
if (!vis[i]) dfs(i, 0), ans ^= f[i];
if (ans) cout << "Alice" << endl;
else cout << "Bob" << endl;
}
}