\(\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);
}

[清华集训2016] Alice 和 Bob 又在玩游戏的更多相关文章

  1. uoj266[清华集训2016]Alice和Bob又在玩游戏(SG函数)

    uoj266[清华集训2016]Alice和Bob又在玩游戏(SG函数) uoj 题解时间 考虑如何求出每棵树(子树)的 $ SG $ . 众所周知一个状态的 $ SG $ 是其后继的 $ mex $ ...

  2. UOJ #266 【清华集训2016】 Alice和Bob又在玩游戏

    题目链接:Alice和Bob又在玩游戏 这道题就是一个很显然的公平游戏. 首先\(O(n^2)\)的算法非常好写.暴力枚举每个后继计算\(mex\)即可.注意计算后继的时候可以直接从父亲转移过来,没必 ...

  3. [UOJ266]Alice和Bob又在玩游戏

    [UOJ266]Alice和Bob又在玩游戏 Tags:题解 作业部落 评论地址 TAG:博弈 题意 不同于树的删边游戏,删掉一个点删去的是到根的路径 题解 这题只和计算\(SG\)有关,博弈的有关内 ...

  4. [BZOJ4730][清华集训2016][UOJ266] Alice和Bob又在玩游戏

    题意:俩智障又在玩游戏.规则如下: 给定n个点,m条无向边(m<=n-1),保证无环,对于每一个联通块,编号最小的为它们的根(也就是形成了一片这样的森林),每次可以选择一个点,将其本身与其祖先全 ...

  5. bzoj4730: Alice和Bob又在玩游戏

    Description Alice和Bob在玩游戏.有n个节点,m条边(0<=m<=n-1),构成若干棵有根树,每棵树的根节点是该连通块内编号最 小的点.Alice和Bob轮流操作,每回合 ...

  6. UOJ#266. 【清华集训2016】Alice和Bob又在玩游戏 博弈,DSU on Tree,Trie

    原文链接https://www.cnblogs.com/zhouzhendong/p/UOJ266.html 题解 首先我们可以直接暴力 $O(n^2)$ 用 sg 函数来算答案. 对于一个树就是枚举 ...

  7. uoj#266. 【清华集训2016】Alice和Bob又在玩游戏(博弈论)

    传送门 完了我连sg函数是个啥都快忘了 设\(sg[u]\)为以\(u\)为根节点的子树的\(sg\)函数值,\(rem[u]\)表示\(u\)到根节点的路径删掉之后剩下的游戏的异或值 根节点\(u\ ...

  8. 【清华集训2016】Alice和Bob又在玩游戏

    不难的题目.因为SG性质,所以只需要对一棵树求出. 然后如果发现从上往下DP不太行,所以从下往上DP. 考虑一个点对子树的合并,考虑下一个删的点在哪一个子树,那么剩下的状态实际上就是把一个子树所有能达 ...

  9. UOJ 266 - 【清华集训2016】Alice和Bob又在玩游戏(SG 定理+01-trie)

    题面传送门 神仙题. 首先注意到此题的游戏是一个 ICG,故考虑使用 SG 定理解决这个题,显然我们只需对每个连通块计算一遍其 SG 值异或起来检验是否非零即可.注意到我们每删除一个点到根节点的路径后 ...

  10. 【bzoj4730】 Alice和Bob又在玩游戏

    http://www.lydsy.com/JudgeOnline/problem.php?id=4730 (题目链接) 题意 给出一个森林,两个人轮流操作,每次把一个节点以及它的祖先全部抹去,无节点可 ...

随机推荐

  1. NLP手札1. 金融信息负面及主体判定方案梳理&代码实现

    这个系列会针对NLP比赛,经典问题的解决方案进行梳理并给出代码复现~也算是找个理由把代码从TF搬运到torch.Chapter1是CCF BDC2019的赛题:金融信息负面及主体判定,属于实体关联的情 ...

  2. C# Aspose.Words.Document.PageCount 踩坑笔记(获取文档页数)

    事情是这样的,我需要获取Word文档的页数,结果就遇到了这个坑人的问题. var doc = new Aspose.Words.Document(@"相对路径"); doc.Pag ...

  3. windows使用pyinstaller 打包sklearn模块出现死循环报错

    前言 解决这个让我花费了很长时间, 我这里说的死循环,不是正常通过 --hidden-import能解决的问题. 因为我也查询了很多资料 但是无一例外都失败了(能通过 –hidden-import 解 ...

  4. Fastjsonfan反序列化(1)

    前言 之前只是对FastJson漏洞有简单的一个认知,虽然由于网上fastjson漏洞调试的文章很多,但是真正有着自己的理解并能清楚的讲述出来的文章少之又少.大多文章都是对已知的漏洞调用流程做了大量分 ...

  5. 【大数据面试】【数仓项目】分层:ODS层、DWD层、DWS层、ADS层构成、操作

    一.ODS层 1.保持数据原貌,不做任何修改 2.数据压缩:LZO压缩,减少磁盘空间 3.创建的是分区表:可以防止后续的全表扫描 包括 用户行为:string line dt    ods_start ...

  6. 帮你短时间拿下Git,Git详细教程(浓缩的都是精华)

    Git学习笔记 Git是一个开源的分布式版本控制系统,可以有效.高速地处理从很小到非常大的项目版本管理. 在团队开发中git是必不可少的,它是目前为止最流行的版本控制工具 Git是免费.开源的,由Li ...

  7. 边框 display属性 盒子模型 浮动 溢出 定位 z-index

    目录 边框 隐藏属性 钓鱼网站 display visibility 盒子模型 调整方式 浮动 溢出 圆形头像的制作 定位 z-index属性 边框 /*border-left-width: 5px; ...

  8. C++进阶(map+set容器模拟实现)

    关联式容器 关联式容器也是用来存储数据的,与序列式容器(如vector.list等)不同的是,其里面存储的是<key,value>结构的键值对,在数据检索时比序列式容器效率更高.今天要介绍 ...

  9. [OpenCV实战]28 基于OpenCV的GUI库cvui

    目录 1 cvui的使用 1.1 如何在您的应用程序中添加cvui 1.2 基本的"hello world"应用程序 2 更高级的应用 3 代码 4 参考 有很多很棒的GUI库,例 ...

  10. 【大型软件开发】浅谈大型Qt软件开发(一)开发前的准备——在着手开发之前,我们要做些什么?

    前言 最近我们项目部的核心产品正在进行重构,然后又是年底了,除了开发工作之外项目并不紧急,加上加班时间混不够了....所以就忙里偷闲把整个项目的开发思路聊一下,以供参考. 鉴于接下来的一年我要操刀这个 ...