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

不难的题目。因为SG性质,所以只需要对一棵树求出。

然后如果发现从上往下DP不太行,所以从下往上DP。

考虑一个点对子树的合并,考虑下一个删的点在哪一个子树,那么剩下的状态实际上就是把一个子树所有能达到的状态异或上一个数。

此时还有不到子树的状态,直接插入子树SG异或值。

所以显然,就是维护一个支持全部异或,以及状态合并,查询mex的数据结构,直接trie合并带tag就好了。

时空复杂度 \(O(n \log n)\)

#include <bits/stdc++.h>

const int MAXN = 100010;
const int UP = 21;
const int MN = MAXN * (UP + 1);
int son[MN][2], val[MN], tag[MN], txt;
void mktag(int u, int v) { tag[u] ^= v; }
void pushdown(int u, int dig) {
    if (tag[u]) {
        if (son[u][0]) mktag(son[u][0], tag[u]);
        if (son[u][1]) mktag(son[u][1], tag[u]);
        if (tag[u] >> dig & 1)
            std::swap(son[u][0], son[u][1]);
        tag[u] = 0;
    }
}
void update(int u) { val[u] = val[son[u][0]] + val[son[u][1]]; }
void insert(int & x, int v, int dig = UP) {
    if (!x) x = ++txt, son[x][0] = son[x][1] = val[x] = tag[x] = 0;
    if (dig == -1) return (void) (val[x] = 1);
    pushdown(x, dig);
    insert(son[x][v >> dig & 1], v, dig - 1);
    update(x);
}
int merge(int x, int y, int dig = UP) {
    if (!x || !y) return x | y;
    pushdown(x, dig), pushdown(y, dig);
    son[x][0] = merge(son[x][0], son[y][0], dig - 1);
    son[x][1] = merge(son[x][1], son[y][1], dig - 1);
    if (dig != -1) update(x);
    return x;
}
int query(int u, int dig = UP) {
    if (!u) return 0;
    if (val[son[u][0]] < (1 << dig)) return query(son[u][0], dig - 1);
    return query(son[u][1], dig - 1) | 1 << dig;
}
int head[MAXN], nxt[MAXN << 1], to[MAXN << 1], tot;
void addedge(int b, int e) {
    nxt[++tot] = head[b]; to[head[b] = tot] = e;
    nxt[++tot] = head[e]; to[head[e] = tot] = b;
}
int sg[MAXN], rts[MAXN];
bool vis[MAXN];
int solve(int u, int fa = 0) {
    vis[u] = true;
    int pre = 0, & rt = rts[u];
    for (int i = head[u]; i; i = nxt[i]) if (to[i] != fa)
        solve(to[i], u), pre ^= sg[to[i]];
    for (int i = head[u]; i; i = nxt[i])
        if (to[i] != fa) {
            mktag(rts[to[i]], pre ^ sg[to[i]]);
            rt = merge(rt, rts[to[i]]);
        }
    insert(rt, pre);
    sg[u] = query(rt);
    return rt;
}
int n, m;
int main() {
    std::ios_base::sync_with_stdio(false), std::cin.tie(0);
    int T; std::cin >> T;
    while (T --> 0) {
        std::cin >> n >> m;
        for (int i = 1; i <= m; ++i) {
            int t1, t2; std::cin >> t1 >> t2;
            addedge(t1, t2);
        }
        int ans = 0;
        for (int i = 1; i <= n; ++i) if (!vis[i])
            solve(i), ans ^= sg[i];
        std::cout << (ans ? "Alice" : "Bob") << '\n';
        tot = 0; memset(head, 0, n + 1 << 2);
        memset(vis, 0, n + 1); txt = 0;
        memset(rts, 0, n + 1 << 2);
    }
    return 0;
}

原文地址:https://www.cnblogs.com/daklqw/p/11563341.html

时间: 2024-10-12 23:01:05

【清华集训2016】Alice和Bob又在玩游戏的相关文章

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

题意:俩智障又在玩游戏.规则如下: 给定n个点,m条无向边(m<=n-1),保证无环,对于每一个联通块,编号最小的为它们的根(也就是形成了一片这样的森林),每次可以选择一个点,将其本身与其祖先全部删除,不能操作者输.判断先手胜负. 题解:比较神的一道题. 我们现在要解决的问题是怎么求解一棵子树的SG值,首先把根删掉的情况考虑,这很好办,直接把子树的sg异或起来就好,关键是如果删除点在子树里怎么办. 这里用到了一个巧妙的东西,trie.怎么会用这个呢?因为删除子树里的节点就相当于是子树里这种对应的

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

Link Multi-SG模板题. 设\(sg_u\)为\(u\)子树的SG函数值,\(S_u\)为\(u\)到删除根节点的路径之后剩下的游戏的SG函数值的异或和. 根节点的\(S\)就是它所有子树的SG函数值的疑惑和. 在求出\(S_u\)之后,它的所有儿子\(v\)的\(S_v\)需要异或上\(S_u\oplus sg_v\). 然后\(sg_u=\operatorname{mex}\{S_v|v\in son_u\}\). 那么使用Trie树维护一下就好了. #include<cstdio

【UOJ274】【清华集训2016】温暖会指引我们前行 LCT

[UOJ274][清华集训2016]温暖会指引我们前行 任务描述 虽然小R住的宿舍楼早已来了暖气,但是由于某些原因,宿舍楼中的某些窗户仍然开着(例如厕所的窗户),这就使得宿舍楼中有一些路上的温度还是很低. 小R的宿舍楼中有n个地点和一些路,一条路连接了两个地点,小R可以通过这条路从其中任意一个地点到达另外一个地点.但在刚开始,小R还不熟悉宿舍楼中的任何一条路,所以他会慢慢地发现这些路,他在发现一条路时还会知道这条路的温度和长度.每条路的温度都是互不相同的. 小R需要在宿舍楼中活动,每次他都需要从

bzoj 4736 /uoj274【清华集训2016】温暖会指引我们前行 lct

[清华集训2016]温暖会指引我们前行 统计 描述 提交 自定义测试 寒冬又一次肆虐了北国大地 无情的北风穿透了人们御寒的衣物 可怜虫们在冬夜中发出无助的哀嚎 “冻死宝宝了!” 这时 远处的天边出现了一位火焰之神 “我将赐予你们温暖和希望!” 只见他的身体中喷射出火焰之力 通过坚固的钢铁,传遍了千家万户 这时,只听见人们欢呼 “暖气来啦!” 任务描述 虽然小R住的宿舍楼早已来了暖气,但是由于某些原因,宿舍楼中的某些窗户仍然开着(例如厕所的窗户),这就使得宿舍楼中有一些路上的温度还是很低. 小R的

UOJ 274 【清华集训2016】温暖会指引我们前行 ——Link-Cut Tree

魔法森林高清重置, 只需要维护关于t的最大生成树,然后链上边权求和即可. 直接上LCT 调了将近2h 吃枣药丸 #include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; #define F(i,j,k) for (int i=j;i<=k;++i) #define D(i,j,k) for (int i=j;i&g

【UOJ】#273. 【清华集训2016】你的生命已如风中残烛

题目链接:http://uoj.ac/problem/273 $${Ans=\frac{\prod _{i=1}^{m}i}{w-n+1}}$$ 1 #include<iostream> 2 #include<cstdio> 3 #include<algorithm> 4 #include<vector> 5 #include<cstdlib> 6 #include<cmath> 7 #include<cstring> 8

【20161203-20161208】清华集训2016滚粗记&amp;&amp;酱油记&amp;&amp;游记

先挖坑(这个blog怎么变成游记专用了--) #include <cstdio> using namespace std; int main(){ puts("转载请注明出处:http://www.cnblogs.com/wangyurzee7/"); puts("谢谢您的配合"); puts("by wangyurzee7"); return 0; }

UOJ268 [清华集训2016] 数据交互 【动态DP】【堆】【树链剖分】【线段树】

题目分析: 不难发现可以用动态DP做. 题目相当于是要我求一条路径,所有与路径有交的链的代价加入进去,要求代价最大. 我们把链的代价分成两个部分:一部分将代价加入$LCA$之中,用$g$数组保存:另一部分将代价加在整条链上,用$d$数组保存. 这时候我们可以发现,一条从$u$到$v$的路径的代价相当于是$d[LCA(u,v)]+\sum_{x \in edge(u,v)}g[x]$. 如果是静态的,可以用树形DP解决. 看过<神奇的子图>的同学都知道,叶子结点是从它的儿子中取两个最大的出来,所

UOJ273 [清华集训2016] 你的生命已如风中残烛 【数学】

题目分析: 把$0$卡牌看成$-1$.题目要求前缀和始终大于等于$1$. 最后添加一个$-1$,这样除了最后一位之外大于等于1,最后一位等于0. 构造圆排列.这样的话一个圆排列只有一个满足的情况,然后考虑我们多出了一个$-1$,所以除去. 代码: 1 #include<bits/stdc++.h> 2 using namespace std; 3 4 const int maxn = 45; 5 const int mod = 998244353; 6 7 int n,m; 8 int a[m