SPOJ COT3 Combat on a tree(Trie树、线段树的合并)
题目链接: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树、线段树的合并)的更多相关文章
- SPOJ COT3.Combat on a tree(博弈论 Trie合并)
题目链接 \(Description\) 给定一棵\(n\)个点的树,每个点是黑色或白色.两个人轮流操作,每次可以选一个白色的点,将它到根节点路径上的所有点染黑.不能操作的人输,求先手是否能赢.如果能 ...
- SPOJ COT3 - Combat on a tree
/* 考虑直接使用暴力来算的话 SG[i]表示以i为根的子树的SG值, 然后考虑枚举删除那个子树节点, 然后求拆成的树的sg异或值, 求mex即可 复杂度三次方 然后考虑尝试 整体来做 发现对于每次子 ...
- BZOJ - 2588 Spoj 10628. Count on a tree (可持久化线段树+LCA/树链剖分)
题目链接 第一种方法,dfs序上建可持久化线段树,然后询问的时候把两点之间的所有树链扒出来做差. #include<bits/stdc++.h> using namespace std; ...
- SPOJ 10628. SPOJ COT Count on a tree 可持久化线段树
这题是裸的主席树,每个节点建一棵主席树,再加个lca就可以了. 历尽艰辛,终于A掉了这一题,这般艰辛也显示出了打代码的不熟练. 错误:1.lca倍增的时候i和j写反了,RE了5次,实在要吸取教训 2. ...
- POJ.3321 Apple Tree ( DFS序 线段树 单点更新 区间求和)
POJ.3321 Apple Tree ( DFS序 线段树 单点更新 区间求和) 题意分析 卡卡屋前有一株苹果树,每年秋天,树上长了许多苹果.卡卡很喜欢苹果.树上有N个节点,卡卡给他们编号1到N,根 ...
- 2016湖南省赛 I Tree Intersection(线段树合并,树链剖分)
2016湖南省赛 I Tree Intersection(线段树合并,树链剖分) 传送门:https://ac.nowcoder.com/acm/contest/1112/I 题意: 给你一个n个结点 ...
- 浅谈可持久化Trie与线段树的原理以及实现(带图)
浅谈可持久化Trie与线段树的原理以及实现 引言 当我们需要保存一个数据结构不同时间的每个版本,最朴素的方法就是每个时间都创建一个独立的数据结构,单独储存. 但是这种方法不仅每次复制新的数据结构需要时 ...
- 浅谈树套树(线段树套平衡树)&学习笔记
0XFF 前言 *如果本文有不好的地方,请在下方评论区提出,Qiuly感激不尽! 0X1F 这个东西有啥用? 树套树------线段树套平衡树,可以用于解决待修改区间\(K\)大的问题,当然也可以用 ...
- 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 ...
随机推荐
- android硬件调试之无法识别android设备解决办法
DDMS 中无法识别华为荣耀六手机, 用豌豆荚开始显示无法连接, 用豌豆荚安装完驱动后,就可以连接了 http://www.zhihu.com/question/30588024 http://w ...
- $watch、$digest、$apply
$watch.$digest.$apply $watch 代表的就是对数据源的监听,当数据源发生变化,就会触发第二个参数的回调函数 $digest 代表触发一个数据源变化的事件 $apply 代表对于 ...
- C/C++ 获取汉字拼音首字母
#include <stdint.h> #include <stdio.h> #include <ctype.h> #include <string.h> ...
- jdk 8 lambda表达式 及在Android Studio的使用示例
以前觉得java让人觉得有趣的一个特点是支持:匿名内部类,而最近发现jdk8已支持lambda并有更简便的方式,来实现匿名内部类. 这会让程序员更舒服,更喜欢java. 多年前觉得java语法和C#语 ...
- .NET对象与Windows句柄(三):句柄泄露实例分析
在上篇文章.NET对象与Windows句柄(二):句柄分类和.NET句柄泄露的例子中,我们有一个句柄泄露的例子.例子中多次创建和Dispose了DataReceiver和DataAnalyzer对象, ...
- Bundle文件的创建和使用(一)
经常会出现某个需求:将自己的模块或者开放类,封装成静态库给其他人提供方便的调用. 但是当你的模块中需要大量使用xib,图片,音频或者其他资源文件时,无法添加至静态库.这个时候就需要将一些资源文件封装至 ...
- Intersection of Two Linked Lists | LeetCode
利用两个栈,然后分别存储每一个链表. 继而,相继pop相同的节点. 有些细节需要注意,请看最后的返回值是如何处理的. /** * Definition for singly-linked list. ...
- 1057 N的阶乘
1057 N的阶乘 基准时间限制:1 秒 空间限制:131072 KB 输入N求N的阶乘的准确值. Input 输入N(1 <= N <= 10000) Output 输出N的阶乘 Inp ...
- Magento打印(配送单、退款单、发票)时PDF中的乱码问题
我使用Magento1.4.2,在其自带的TTF文件不能很好地解析中文字符,TTF文件的位置在网站根目录下的/lib/LinLibertineFont/中.打印的中文字符都是这样的 解决方法: 1.在 ...
- curl get post 数据
1.get方式传值 function testGet(){ $ch = curl_init (); //初始化一个cURL会话 $url = "127.0.0.1/testPage?test ...