[清华集训2016] Alice 和 Bob 又在玩游戏

\(\text{Solution}\)

第一道有向图 \(SG\) 函数的博弈论
有根树,设 \(f[x]\) 表示以 \(x\) 为根子树的 \(SG\)
对于分裂的图的 \(SG\) 值为每个小联通图 \(SG\) 的异或值
考虑每次操作后形成的图的 \(SG\) 值,这些为原图的可达状态
那么 \(f[x]\) 就是这些值的 \(mex\)
为方便转移,设 \(g[x]\) 表示 \(x\) 直系儿子的 \(SG\) 的异或值
那么一次操作选择 \(v\) 后裂成的图的 \(SG\)\(g[v]\oplus f[v]\oplus g[fa[v]]\oplus f[fa[fa[v]]]\oplus g[fa[fa[v]]]\oplus...\oplus g[x]\)
对于所有这些值取 \(mex\) 即为 \(f[x]\)
那么这样做就是 \(O(n^2)\)
观察一下 \(x\) 的所有后继状态的 \(SG\)\(fa[x]\) 的后继状态 \(SG\) 的关系
发现就是对所有直系儿子 \(x\) 的所有后继状态 \(SG\) 值异或上了 \(f[x]\oplus g[fa[x]]\)
还多了一个 \(g[fa[x]]\)
这些就是 \(fa[x]\) 的后继状态的 \(SG\) 值,对其取 \(mex\) 即为 \(f[fa[x]]\)
那么我们就需要记录 \(x\) 的所有后继状态的 \(SG\) 值,然后要支持子树 \(SG\) 值的合并,支持取 \(mex\) 操作
类似线段树, \(Trie\) 合并即可
\(mex\) 操作就看子树节点个数满没满,优先走左子树
打异或标记就是在根打异或值,用前 \(pushdown\)

\(\text{Code}\)

#include <cstdio>
#include <iostream>
#define IN inline
using namespace std;

const int N = 1e5 + 5, LG = 18, M = N * LG * 2;
int T, n, m, h[N], tot, f[N], g[N], vis[N];
struct edge{int to, nxt;}e[N << 1];
IN void add(int x, int y){e[++tot] = edge{y, h[x]}, h[x] = tot;}

int size, rt[N], tr[M][2], tag[M], siz[M], stk[LG + 1];
IN void pushdown(int p, int w)
{
	if ((tag[p] >> w) & 1) swap(tr[p][0], tr[p][1]);
	if (tr[p][0]) tag[tr[p][0]] ^= tag[p];
	if (tr[p][1]) tag[tr[p][1]] ^= tag[p];
	tag[p] = 0;
}
IN void pushup(int p){siz[p] = siz[tr[p][0]] + siz[tr[p][1]];}
IN int NewNode(){int p = ++size; tr[p][0] = tr[p][1] = tag[p] = siz[p] = 0; return p;}
int Merge(int x, int y, int d)
{
	if (!x || !y) return x | y;
	if (d == -1) return x;
	pushdown(x, d), pushdown(y, d);
	tr[x][0] = Merge(tr[x][0], tr[y][0], d - 1);
	tr[x][1] = Merge(tr[x][1], tr[y][1], d - 1);
	pushup(x); return x;
}
IN void Insert(int &p, int x)
{
	if (!p) p = NewNode();
	int u = p, ch, top = 0;
	for(int i = LG; i >= 0; i--)
	{
		stk[++top] = u, pushdown(u, i), ch = (x >> i) & 1;
		if (!tr[u][ch]) tr[u][ch] = NewNode();
		u = tr[u][ch];
	}
	siz[u] = 1; for(; top; --top) pushup(stk[top]);
}
IN int Mex(int p)
{
	if (!p) return 0;
	int res = 0;
	for(int i = LG; i >= 0; --i)
	{
		pushdown(p, i);
		if (siz[tr[p][0]] < (1 << i)) p = tr[p][0];
		else p = tr[p][1], res |= (1 << i);
	}
	return res;
}
void dfs(int x, int fa)
{
	for(int i = h[x]; i; i = e[i].nxt)
		if (e[i].to ^ fa) dfs(e[i].to, x), g[x] ^= f[e[i].to];
	for(int i = h[x]; i; i = e[i].nxt)
	if (e[i].to ^ fa)
		tag[rt[e[i].to]] ^= g[x] ^ f[e[i].to], rt[x] = Merge(rt[x], rt[e[i].to], LG);
	vis[x] = 1, Insert(rt[x], g[x]), f[x] = Mex(rt[x]);
}

int main()
{
	scanf("%d", &T);
	for(int sg; T; --T)
	{
		scanf("%d%d", &n, &m), size = tot = sg = 0; 
		for(int i = 1, x, y; i <= m; i++) scanf("%d%d", &x, &y), add(x, y), add(y, x);
		for(int i = 1; i <= n; i++) if (!vis[i]) dfs(i, 0), sg ^= f[i];
		puts(sg ? "Alice" : "Bob");
		for(int i = 1; i <= n; i++) h[i] = vis[i] = f[i] = g[i] = rt[i] = 0;
	}
}

对于 \(\text{[BZOJ4134] ljw 和 lzr 的 hack 比赛([JZOJ4401]dierti)}\) 这题
多了输出第一步,那么能胜利的第一步就是这一步后分裂图的 \(SG\) 值为 \(0\),这和 \(f\) 的转移是类似的

\(\text{Code}\)

#include <cstdio>
#include <iostream>
#define IN inline
using namespace std;

const int N = 1e5 + 5, LG = 18, M = N * LG * 2;
int n, a[N], h[N], tot, ok[N], f[N], g[N];
struct edge{int to, nxt;}e[N << 1];
IN void add(int x, int y){e[++tot] = edge{y, h[x]}, h[x] = tot;}

int size, rt[N], tr[M][2], tag[M], siz[M], stk[LG + 1];
IN void pushdown(int p, int w)
{
	if ((tag[p] >> w) & 1) swap(tr[p][0], tr[p][1]);
	tag[tr[p][0]] ^= tag[p], tag[tr[p][1]] ^= tag[p], tag[p] = 0;
}
IN void pushup(int p){siz[p] = siz[tr[p][0]] + siz[tr[p][1]];}
int Merge(int x, int y, int d)
{
	if (!x || !y) return x | y;
	if (d == -1) return x;
	pushdown(x, d), pushdown(y, d);
	tr[x][0] = Merge(tr[x][0], tr[y][0], d - 1);
	tr[x][1] = Merge(tr[x][1], tr[y][1], d - 1);
	pushup(x); return x;
}
IN void Insert(int &p, int x)
{
	if (!p) p = ++size;
	int u = p, ch, top = 0;
	for(int i = LG; i >= 0; i--)
	{
		stk[++top] = u, pushdown(u, i), ch = (x >> i) & 1;
		if (!tr[u][ch]) tr[u][ch] = ++size;
		u = tr[u][ch];
	}
	siz[u] = 1; for(; top; --top) pushup(stk[top]);
}
IN int Mex(int p)
{
	int res = 0;
	for(int i = LG; i >= 0; --i)
	{
		pushdown(p, i);
		if (siz[tr[p][0]] < (1 << i)) p = tr[p][0];
		else p = tr[p][1], res |= (1 << i);
	}
	return res;
}
void dfs(int x, int fa)
{
	for(int i = h[x]; i; i = e[i].nxt)
		if (e[i].to ^ fa) dfs(e[i].to, x), g[x] ^= f[e[i].to];
	if (!a[x]) Insert(rt[x], g[x]);
	for(int i = h[x]; i; i = e[i].nxt)
	if (e[i].to ^ fa)
		tag[rt[e[i].to]] ^= g[x] ^ f[e[i].to], rt[x] = Merge(rt[x], rt[e[i].to], LG);
	f[x] = Mex(rt[x]);
}
void Get_First_Step(int x, int fa, int v)
{
	v ^= g[x];
	if (!a[x] && !v) ok[x] = 1;
	for(int i = h[x]; i; i = e[i].nxt)
		if (e[i].to ^ fa) Get_First_Step(e[i].to, x, v ^ f[e[i].to]);
}

int main()
{
	scanf("%d", &n);
	for(int i = 1; i <= n; i++) scanf("%d", &a[i]);
	for(int i = 1, x, y; i < n; i++) scanf("%d%d", &x, &y), add(x, y), add(y, x);
	dfs(1, 0);
	if (!f[1]){printf("-1\n"); return 0;}
	Get_First_Step(1, 0, 0);
	for(int i = 1; i <= n; i++) if (ok[i]) printf("%d\n", i);
}
posted @ 2021-12-31 08:07  leiyuanze  阅读(132)  评论(0编辑  收藏  举报