[SPOJ COT3] SG函数 Trie树 线段树合并

题目

  

分析

  

  

实现

#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cctype>
#include <algorithm>
#include <vector>
using namespace std;

#define F(i, a, b) for (register int i = (a); i <= (b); i++)
#define fore(it, x) for (register vector<int>::iterator it = g[x].begin(); it != g[x].end(); it++)

const int N = 100005;
const int B = 20;
const int S = 3000000;

int n, col[N];
vector<int> g[N];

int f[N], rt[N], tot;
int c[S][2], tag[S]; bool full[S];

bool s[N];

inline int rd(void) {
    int f = 1; char c = getchar(); for (; !isdigit(c); c = getchar()) if (c == ‘-‘) f = -1;
    int x = 0; for (; isdigit(c); c = getchar()) x = x*10+c-‘0‘; return x*f;
}

inline void Push(int x, int w, int bit) {
    if (x > 0) {
        if (w >> bit & 1)
            swap(c[x][0], c[x][1]);
        tag[x] ^= w;
    }
}
inline void Clear(int x, int bit) {
    if (!tag[x]) return;
    Push(c[x][0], tag[x], bit-1);
    Push(c[x][1], tag[x], bit-1);
    tag[x] = 0;
}
inline void Merge(int &x, int y, int bit) {
    if (!x) { x += y; return; }
    if (bit == -1) { full[x] |= full[y]; return; }
    Clear(x, bit);
    Clear(y, bit);
    Merge(c[x][0], c[y][0], bit-1);
    Merge(c[x][1], c[y][1], bit-1);
    full[x] = full[c[x][0]] & full[c[x][1]];
}
inline void Insert(int &x, int bit, int w) {
    if (!x) x = ++tot;
    if (bit == -1) { full[x] = true; return; }
    Clear(x, bit);
    Insert(c[x][w >> bit & 1], bit-1, w);
    full[x] = full[c[x][0]] & full[c[x][1]];
}
inline int Mex(int x, int bit) {
    if (!x || bit == -1) return 0;
    Clear(x, bit);
    return full[c[x][0]] ? (1<<bit) + Mex(c[x][1], bit-1) : Mex(c[x][0], bit-1);
}

void Solve(int x, int pre) {
    fore(it, x) if (*it != pre)
        Solve(*it, x);

    int sum = 0; fore(it, x) if (*it != pre) sum ^= f[*it];
    fore(it, x) if (*it != pre) {
        Push(rt[*it], sum ^ f[*it], B);
        Merge(rt[x], rt[*it], B);
    }
    if (!col[x]) Insert(rt[x], B, sum);

    f[x] = Mex(rt[x], B);
}
void Find(int x, int pre, int tar) {
    int sum = 0; fore(it, x) if (*it != pre) sum ^= f[*it];
    if (!col[x] && sum == tar) s[x] = true;
    fore(it, x) if (*it != pre)
        Find(*it, x, tar ^ sum ^ f[*it]);
}

int main(void) {
    #ifndef ONLINE_JUDGE
        freopen("game.in", "r", stdin);
        freopen("game.out", "w", stdout);
    #endif

    n = rd();
    F(i, 1, n) col[i] = rd();
    F(i, 1, n-1) {
        int x = rd(), y = rd();
        g[x].push_back(y), g[y].push_back(x);
    }

    Solve(1, -1);

    if (!f[1]) puts("-1");
    else {
        Find(1, -1, 0);
        //    F(i, 1, n) printf("%d\n", f[i]);
        F(i, 1, n) if (s[i]) printf("%d\n", i);
    }

    return 0;
}
时间: 2024-07-29 12:45:27

[SPOJ COT3] SG函数 Trie树 线段树合并的相关文章

【博弈论】【SG函数】【线段树】Petrozavodsk Summer Training Camp 2016 Day 9: AtCoder Japanese Problems Selection, Thursday, September 1, 2016 Problem H. Cups and Beans

一开始有n个杯子,每个杯子里有一些豆子,两个人轮流操作,每次只能将一个豆子移动到其所在杯子之前的某个杯子里,不过可以移动到的范围只有一段区间.问你是否先手必胜. 一个杯子里的豆子全都等价的,因为sg函数是异或起来的值,所以一个杯子里如果有偶数个豆子,就没有意义. 用sg(i)表示i杯子中的豆子的sg值,sg(i)就是其所能移动到的那段杯子的区间的sg值的mex(未出现的最小非负整数).可以用线段树去做.是经典问题. 由于每次看似是后缀询问,实则是全局询问,故而主席树完全是多余的. 回顾一下区间m

#树# #线段树#

线段树 线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点. 线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点. 对于线段树中的每一个非叶子节点[a,b],它的左儿子表示的区间为[a,(a+b)/2],右儿子表示的区间为[(a+b)/2+1,b].因此线段树是平衡二叉树,最后的子节点数目为N,即整个线段区间的长度. 模板: 建树 1 void Pushup(int rt){//根节点

HDU 4417 Super Mario ( 超级马里奥 + 主席树 + 线段树/树状数组离线处理 + 划分树 )

HDU 4417 - Super Mario ( 主席树 + 线段树/树状数组离线处理 + 划分树 ) 这道题有很多种做法,我先学习的是主席树.后面陆续补上线段树离线和划分树 题目大意就是给定一个区间给定一个数列,每次要求你查询区间[L,R]内不超过K的数的数量 主席树做法: 最基本的是静态第k大,这里是求静态的 <= K,差不多,在Query操作里面需要修改修改 先建立size棵主席树,然后询问的时候统计的是 第R棵主席树中[1,K]的数量 - 第L-1棵主席树中[1,K]的数量 注意这里下标

SPOJ GSS系列 最大子段和 线段树+树链剖分+splay 1043 1557 1716 2713 2916 4487 6779

最大子段和的各种形式 题解内附每道题的 题意 题目链接 思路 SPOJ 1043 GSS1 静态区间求个最大子段和, 题解 SPOJ 1577 GSS2 和1一样,区别是若区间内存在相同的元素,则该元素只计算一次. 离线一下然后使劲跑.. 题解 SPOJ 1716 GSS3 和1一样,就是要支持单点修改 题解 SPOJ 2713 GSS4 ==普通线段树,感觉和这系列关系不大. 题解 SPOJ 2916 GSS5 题意有点怪,,跟3差不多,就是稍加强了点询问 题解 SPOJ 4487 GSS6

BZOJ_3196_二逼平衡树(树套树:线段树+Treap)

描述 可以处理区间问题的平衡树. 分析 树套树.可以用线段树套Treap.人生第一道树套树的题... op1:如果在整区间,直接在该区间的treap上求解.否则分两个区间求解,然后相加.最后+1. op2:这个不太好直接做,可以二分,每次假定一个值,用这个值去做op1,以此求得一个rank=k+1的数,求rank=k的数等价与求这个数的前驱pre. op3:先删后加. op4&op5:如果在整区间,直接在该区间的treap上求解,否则分量个区间求解,pre取最大值,suc取最小值.注意有些数在有

cf842D 01字典树|线段树 模板见hdu4825

一般异或问题都可以转换成字典树的问题,,我一开始的想法有点小问题,改一下就好了 下面的代码是逆向建树的,数据量大就不行 /*3 01字典树 根据异或性质,a1!=a2 ==> a1^x1^..^xn != a2^x1^..an 把修改转换成不同的询问 先把初始集合里没有的数建立成字典树 每次询问找的是字典树里异或x最小的值 */ #include<bits/stdc++.h> using namespace std; #define maxn 300005 int buf[maxn];

树——线段树

线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点. 对于线段树中的每一个非叶子节点[a,b],它的左儿子表示的区间为[a,(a+b)/2],右儿子表示的区间为[(a+b)/2+1,b].因此线段树是平衡二叉树,最后的子节点数目为N,即整个线段区间的长度. 使用线段树可以快速的查找某一个节点在若干条线段中出现的次数,时间复杂度为O(logN).而未优化的空间复杂度为2N,因此有时需要离散化让空间压缩. 案例:节点更新,查找最小值 #1077

BZOJ 3685: 普通van Emde Boas树( 线段树 )

建颗权值线段树就行了...连离散化都不用... 没加读入优化就TLE, 加了就A掉了...而且还快了接近1/4.... ------------------------------------------------------------------------------------------ #include<bits/stdc++.h> #define rep(i, n) for(int i = 0; i < n; i++) #define clr(x, c) memset(x

【BZOJ-3306】树 线段树 + DFS序

3306: 树 Time Limit: 10 Sec  Memory Limit: 256 MBSubmit: 792  Solved: 262[Submit][Status][Discuss] Description 给定一棵大小为 n 的有根点权树,支持以下操作: • 换根 • 修改点权      • 查询子树最小值 Input 第一行两个整数 n, Q ,分别表示树的大小和操作数. 接下来n行,每行两个整数f,v,第i+1行的两个数表示点i的父亲和点i的权.保证f < i.如 果f = 0