题目链接:http://www.spoj.com/problems/COT3/

Alice and Bob are playing a game on a tree of n nodes.Each node is either black or white initially.

They take turns to do the following operation:
Choose a white node v from the current tree;
Color all white nodes on Path(1,v) to black.

The player who takes the last turn wins.

Now Alice takes the first turn.Help her find out if she can win when they both use optimal strategy.

Input

The first line of input contains a integer n representing the number of nodes in the tree. 1<=n<=100000

The second line contains n intergers c1,c2,..cn.0<=ci<=1.
ci=0 means the ith node is white initially and ci=1 means black.

Next n-1 lines describes n-1 edges in the tree.Each line contains two integers u and v,means there is a edge connecting u and v.

Output

If Alice can't win print -1.

Otherwise determine all the nodes she can choose in the first turn in order to win.Print them in ascending order.

题目大意:(以下题意摘自《线段树的合并》——黄嘉泰,标点符号有改动……)

给定一棵n个点的有根树,每个点是黑的或者白的。 两个人在树上博弈,轮流进行以下操作: 选择一个当前为白色的点u,把u到根路径上的所有点涂黑。不能操作者输,判断两人都用最优策略进行游戏时的胜负情况,并输出第一个人第一步所有可行的决策。

思路:(以下思路摘自《线段树的合并》——黄嘉泰)

由公平组合游戏的性质不难想到以下的dp:
---下面的所有+运算都表示xor
dp[u]表示只考虑子树u的SG值
g[u][v]表示只考虑子树u,v是u的某个白色后代(可能为u),第一步选择了v,将u到v全部涂黑后的局面的SG值
dp[u]=mex(g[u]),关键是求g[u]
---当u是白色时,新增g[u][u]=sigma{dp[v]|v是u的儿子}
---g[u][w]=g[v][w]+sigma{dp[v]|v是u的儿子且v!=branch[w]} // 注释:branch[w]=v即,v是u的儿子且v是w的祖先
---g[u][w]=g[v][w]+g[u][u]+dp[branch[w]] // 注释:这一行是上一行的解,其中g[u][u]=sigma{dp[v]|v是u的儿子}

$O(n^2)$
我们可以做的更好
注意到转移过程中,来自同一子树的g值都被xor上了同一个数,最后所有g值被放在一起进行mex
如果选用某种数据结构,能够快速地完成整体xor,再合并的操作,并且高效地支持mex运算,就可以改进复杂度。
二进制Trie
启发式合并,对最大子树的Trie打标记,其余dp值暴力插入
mex(T)=size(T->l)==cnt(T->l)?size(T->l)+mex(T->r):mex(T->l)
复杂度$O(n log^2n)$

瓶颈是合并操作
二进制Trie和线段树非常类似,使用之前的过程高效合并
传递标记/询问mex/合并树 都是自顶向下的,不矛盾
$O(n logn)$

——————————————————————————摘录完毕的分割线——————————————————————————————————

关于合并操作可以去看《线段树的合并》——黄嘉泰。

关于输出解,随便DFS一下即可,详细可以见dfs_ans函数。

PS:这东东就没有什么高大上的名字吗?

PS2:玛雅AC之后发现忘了输出-1,唉不管了。

代码(C++14 0.55S):

 #include <bits/stdc++.h>
using namespace std;
#define FOR(i, n) for(int i = 0; i < n; ++i) const int MAXV = ;
const int MAXE = MAXV << ; const int white = ;
int max_log; int head[MAXV], color[MAXV], ecnt;
int to[MAXE], nxt[MAXE];
int n; void initGraph() {
memset(head + , -, n * sizeof(int));
ecnt = ;
} void add_edge(int u, int v) {
to[ecnt] = v; nxt[ecnt] = head[u]; head[u] = ecnt++;
to[ecnt] = u; nxt[ecnt] = head[v]; head[v] = ecnt++;
} struct Node {
Node* go[];
int size, txor, mex;
};
Node statePool[ * MAXV];
Node *nil, *leaf;
Node* stk[ * MAXV];
int ncnt, top; Node *new_node() {
Node* t = top ? stk[--top] : &statePool[ncnt++];
FOR(i, ) t->go[i] = nil;
t->size = t->txor = t->mex = ;
return t;
} void remove(Node *t) {
stk[top++] = t;
} void initTree() {
nil = statePool;
FOR(i, ) nil->go[i] = nil;
ncnt = ;
leaf = new_node();
leaf->mex = leaf->size = ;
} void pushdown(Node *t, int k) {
if(k > ) {
if((t->txor >> (k - )) & ) swap(t->go[], t->go[]);
FOR(i, ) t->go[i]->txor ^= t->txor;
t->txor = ;
}
} void update(Node *t, int k) {
if(k > ) {
int size = << (k - );
t->mex = (t->go[]->size < size ? t->go[]->mex : size + t->go[]->mex);
t->size = t->go[]->size + t->go[]->size;
}
} Node* merge(Node *a, Node *b, int k) {
if(a == nil) return b;
if(b == nil) return a;
if(a == leaf && b == leaf) return leaf; Node *res = new_node();
pushdown(a, k), pushdown(b, k);
FOR(i, ) res->go[i] = merge(a->go[i], b->go[i], k - );
update(res, k);
remove(a), remove(b);
return res;
} void insert(Node* &t, int k, int val) {
if(k == ) t = leaf;
else {
if(t == nil) t = new_node();
pushdown(t, k);
insert(t->go[(val >> (k - )) & ], k - , val);
update(t, k);
}
} Node *root[MAXV];
int dp[MAXV]; void dfs(int u, int f) {
int tmp = ;
for(int p = head[u]; ~p; p = nxt[p]) {
int v = to[p];
if(v != f) dfs(v, u), tmp ^= dp[v];
}
if(color[u] == white) insert(root[u], max_log, tmp);
for(int p = head[u]; ~p; p = nxt[p]) {
int v = to[p];
if(v == f) continue;
root[v]->txor ^= tmp ^ dp[v];
root[u] = merge(root[u], root[v], max_log);
}
dp[u] = root[u]->mex;
} vector<int> ans; void dfs_ans(int u, int f, int sg) {
int tmp = ;
for(int p = head[u]; ~p; p = nxt[p]) {
int v = to[p];
if(v != f) tmp ^= dp[v];
}
if(color[u] == white && (sg ^ tmp) == ) ans.push_back(u);
for(int p = head[u]; ~p; p = nxt[p]) {
int v = to[p];
if(v != f) dfs_ans(v, u, sg ^ tmp ^ dp[v]);
}
} int main() {
scanf("%d", &n);
for(int i = ; i <= n; ++i) scanf("%d", &color[i]);
initGraph();
for(int i = , u, v; i < n; ++i) {
scanf("%d%d", &u, &v);
add_edge(u, v);
}
initTree();
while(( << max_log) <= n) ++max_log; for(int i = ; i <= n; ++i) root[i] = nil;
dfs(, );
dfs_ans(, , ); sort(ans.begin(), ans.end());
for(int x : ans) printf("%d\n", x);
}

SPOJ COT3 Combat on a tree(Trie树、线段树的合并)的更多相关文章

  1. SPOJ COT3.Combat on a tree(博弈论 Trie合并)

    题目链接 \(Description\) 给定一棵\(n\)个点的树,每个点是黑色或白色.两个人轮流操作,每次可以选一个白色的点,将它到根节点路径上的所有点染黑.不能操作的人输,求先手是否能赢.如果能 ...

  2. SPOJ COT3 - Combat on a tree

    /* 考虑直接使用暴力来算的话 SG[i]表示以i为根的子树的SG值, 然后考虑枚举删除那个子树节点, 然后求拆成的树的sg异或值, 求mex即可 复杂度三次方 然后考虑尝试 整体来做 发现对于每次子 ...

  3. BZOJ - 2588 Spoj 10628. Count on a tree (可持久化线段树+LCA/树链剖分)

    题目链接 第一种方法,dfs序上建可持久化线段树,然后询问的时候把两点之间的所有树链扒出来做差. #include<bits/stdc++.h> using namespace std; ...

  4. SPOJ 10628. SPOJ COT Count on a tree 可持久化线段树

    这题是裸的主席树,每个节点建一棵主席树,再加个lca就可以了. 历尽艰辛,终于A掉了这一题,这般艰辛也显示出了打代码的不熟练. 错误:1.lca倍增的时候i和j写反了,RE了5次,实在要吸取教训 2. ...

  5. POJ.3321 Apple Tree ( DFS序 线段树 单点更新 区间求和)

    POJ.3321 Apple Tree ( DFS序 线段树 单点更新 区间求和) 题意分析 卡卡屋前有一株苹果树,每年秋天,树上长了许多苹果.卡卡很喜欢苹果.树上有N个节点,卡卡给他们编号1到N,根 ...

  6. 2016湖南省赛 I Tree Intersection(线段树合并,树链剖分)

    2016湖南省赛 I Tree Intersection(线段树合并,树链剖分) 传送门:https://ac.nowcoder.com/acm/contest/1112/I 题意: 给你一个n个结点 ...

  7. 浅谈可持久化Trie与线段树的原理以及实现(带图)

    浅谈可持久化Trie与线段树的原理以及实现 引言 当我们需要保存一个数据结构不同时间的每个版本,最朴素的方法就是每个时间都创建一个独立的数据结构,单独储存. 但是这种方法不仅每次复制新的数据结构需要时 ...

  8. 浅谈树套树(线段树套平衡树)&学习笔记

    0XFF 前言 *如果本文有不好的地方,请在下方评论区提出,Qiuly感激不尽! 0X1F 这个东西有啥用? 树套树------线段树套平衡树,可以用于解决待修改区间\(K\)大的问题,当然也可以用 ...

  9. BZOJ 2588: Spoj 10628. Count on a tree 树上跑主席树

    2588: Spoj 10628. Count on a tree Time Limit: 1 Sec Memory Limit: 256 MB 题目连接 http://www.lydsy.com/J ...

随机推荐

  1. Flume协作框架

    1.概述 ->flume的三大功能 collecting, aggregating, and moving 收集 聚合 移动 2.框图 3.架构特点 ->on streaming data ...

  2. Qt系统托盘

    Qt的系统托盘的使用,可比mfc中好多了!他封装了一个专门的QSystemTrayIcon类,建立系统托盘图标.其实在Qt提供的示例程序已经很不错了,$QTDIR\examples\desktop\s ...

  3. Photoshop:通过图片理解通道原理

    电脑上的图像通过色光三原色RGB表现,不同颜色存储在不同的通道,所以RGB模式下有3条通道,CMYK模式有4条通道等等 打开一张照片,查看通道 可以看出,显示越白的地方,对应的原色越深. 默认phot ...

  4. 20145211 《Java程序设计》实验报告二:Java面向对象程序设计

    实验要求 初步掌握单元测试和TDD 理解并掌握面向对象三要素:封装.继承.多态 初步掌握UML建模 熟悉S.O.L.I.D原则 了解设计模式 实验内容 单元测试 面向对象三要素 设计模式初步 练习 实 ...

  5. hdu1231最大连续子序列

    题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=1231 #include<iostream> #include<cstdio> ...

  6. Error Domain=com.alamofire.error.serialization.response Code=-1016 "Request failed: unacceptable con

    AFHTTPSessionManager * manager = [AFHTTPSessionManager manager]; manager.responseSerializer.acceptab ...

  7. c# key event

    使用C#写ui时,希望能够读取到键盘输入的事件,用于快捷键或者其他操作.本文记录操作创建key event的方法. 参考链接: http://csharp.net-informations.com/g ...

  8. Upgrade Image&ntext to varbinarymax&nvarchar(max)

    CREATE PROCEDURE SP_EXEC_WITH_LOG(@I_TICKETNO VARCHAR(10),@I_SQLSTR nvarchar(max))ASBEGIN    DECLARE ...

  9. Shell函数的简单应用

    Shell函数的简单应用 在脚本内给函数传参: #!/bin/bash . /etc/init.d/functions CheckUrl (){ curl -I -s $ | head - } Che ...

  10. iOS: 使用KVO监听控制器中数组的变化

    一.介绍: KVO是一种能动态监听到属性值的改变的方式,使用场景非常广泛,这里我只讲如何监听控制器ViewController中数组的变化. 二.了解: 首先我们应该知道KVO是不能直接监听控制器Vi ...