题目链接: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. jq窗口类小问题

    if ($(window).height() + $(window).scrollTop() >= $(document.body).height() - options.marginBotto ...

  2. 深入理解Javascript闭包 新手版

    一.什么是闭包?  “官方”的解释是:所谓“闭包”,指的是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分. 相信很少有人能直接看懂这句话,因为他描述 ...

  3. PHP正则表达式;数组:for()遍历、 foreach ()遍历、each()list()组合遍历;指针遍历

    正则表达式:    1.定界符号        任何字符,一般用  //    2. 模式修正符i        写在定界符外面后面,可不区分大小写    3.preg_replace($reg,&q ...

  4. This application failed to start because it could not find or load the Qt platform plugin "xcb".

    1.  copy      libQt5DBus.so.5 2.  add    QT_PLUGIN_PATH blog.csdn.net/windows_nt/article/details/242 ...

  5. ECharts SSH+JQueryAjax+Json+JSP将数据库中数据填充到ECharts中

    本文引用自:http://blog.csdn.net/ArcticFoxHan/article/details/38071641   1.导入包,搭建SSH框架 导入Jquery的JS包,<sc ...

  6. java中的trim()

    trim():去掉字符串首尾的空格.但该方法并不仅仅是去除空格,它能够去除从编码'\u0000′ 至 '\u0020′ 的所有字符. 回车换行也在这20个字符 例1: public static vo ...

  7. java分形树

    import java.awt.*; import java.awt.event.*; import java.util.Random; import javax.swing.*; /** * * @ ...

  8. ArcGIS API for Silverlight 调用GP服务加载等值线图层

    原文:ArcGIS API for Silverlight 调用GP服务加载等值线图层 第二篇.Silverlight客户端调用GP服务 利用ArcGIS API for Silverlight实现G ...

  9. 验证整数、小数、实数、有效位小数最简单JavaScript正则表达式

    输入完按回车后即可验证!(自认为最简单!) 正整数:  负整数:  整 数:  正小数:  负小数:  小 数:  实 数:  保留1位小数: 保留2位小数: 保留3位小数: 说明:IE6.0.IE7 ...

  10. 【C++】C++求vector中的最大最小值

    利用algorithm库里的max_element和min_element可以得到vector的最大最小值,配合distance函数可以得到最大值的位置 #include<vector> ...