【BZOJ4134】【树上博弈】【博弈论】【线段树合并】ljw和lzr的hack比赛 题解

Description

分曹射覆蜡灯红,膜拜神犇lzr;
渚清沙白鸟飞回,长跪巨神ljw。
lzr就是被称为hack狂魔的qmqmqm,相比很多人都已经知道了。
ljw虽然没有lzr有名,但是在cf、bc等比赛里的hack次数也是数一数二的。
SD的这两位神犇今天决定进行一场hack比赛。
经过研究,他们决定hack SD的另一位神犇jzh做过的题。
我们设jzh已经做过了n道题。这些题目因为知识点之间的联系而形成了一棵树结构。1号题目(A+B problem)是这棵树的根。
因为slyz也不乏hack高手,所以jzh做的这n道题有些已经被hack了。
现在ljw先手,两人轮流操作。一次操作为:选择一道没有被hack过的题x,然后将x到1的路径上的所有没有被hack过的题全部hack掉。
无法操作者输。
我们假设ljw和lzr的智商都是无比的高(事实也是如此),都会采取对自己最有利的操作。
ljw当然想赢下这场比赛了!于是他想算一下最终他能否赢下比赛。如果他能赢,他还想知道在保证他赢的情况下第一步他选择哪些题。
这么简单的问题ljw神犇当然会了。不过他想考考你。
Input

第一行一个整数n(n<=100000),代表jzh神犇做过的题数。
第二行n个整数,每个整数为0或1。其中第i个数为0代表第i道题还没有被hack,第i个数为1代表第i道题已经被slyz的神犇hack了。
接下来n-1行描述jzh做过的题目形成的树。每行两个整数u,v代表第u道题和第v道题之间有一条边。
Output

如果ljw不能赢得比赛,那么输出-1。
否则升序输出所有题号x。x满足ljw在保证赢的情况下第一步可以选择x。
Sample Input

8

1 1 0 1 0 0 1 0

1 2

1 3

2 6

3 4

3 5

5 7

7 8
Sample Output

5

HINT

数据范围:1<=u,v<=n<=100000

Sg[x]为以x为子树的sg值,rem[x]为删掉从x到根的路径后剩余的游戏的sg值的异或和,x的rem是他所有儿子的sg值的异或和,而对于x的每一个儿子y,y的子树里的所有rem值都会异或rem[x]^sg[y] 然后我们求第一个不出现在子树内的rem集合里的非负整数即是x的sg值对每个点建立一个权值线段树,维护子树内所有的rem值是否出现过,然后在线段树上爬即可查出这个点的sg值。对于一个点,如果没有预先被hack,那么把他的rem插入这个点的线段树中,然后把这棵线段树和他所有儿子的线段树进行线段树合并。由于要把某个子树里的所有值都异或一个数,那么可以把权值线段树的区间设为0~2的幂-1,然后在线段树上打异或标记,如果对应的位是1的话就交换左右儿子即可 最后再dfs一遍树处理每个点的rem判断是否为0即可输出答案

#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <set>
#include <queue>
#include <algorithm>
#include <vector>
#include <cstdlib>
#include <cmath>
#include <ctime>
#include <stack>
#define INF 2147483647
#define LL long long
#define clr(x) memset(x, 0, sizeof x)
#define digit (ch <  '0' || ch >  '9')

using namespace std;

template <class T> inline void read(T &x) {
    int flag = 1; x = 0;
    register char ch = getchar();
    while( digit) { if(ch == '-')  flag = -1; ch = getchar(); }
    while(!digit) { x = (x<<1)+(x<<3)+ch-'0'; ch = getchar(); }
    x *= flag;
}

const int maxn = 100005;
const int maxm = 4000005;
int n,u,v,tot,cnt,N,D;
int a[maxn],t[maxn],sg[maxn],rem[maxn],bl[maxn];
int son[maxm][2],rt[maxm],siz[maxm],rev[maxm];
struct edge { int to,frm; } e[maxn<<1];

inline void add(int x, int y) { e[++cnt].to = y; e[cnt].frm = t[x]; t[x] = cnt; }

inline void bv(int x, int y, int d) {
    rev[x] ^= y;
    if(rev[x]&(1<<d)) swap(son[x][0], son[x][1]);
}

inline void ud(int x) { siz[x] = siz[son[x][0]]+siz[son[x][1]]; }

inline void pd(int x, int d) {
    if(rev[x]) {
        bv(son[x][0], rev[x], d-1);
        bv(son[x][1], rev[x], d-1);
        rev[x] = 0;
    }
}

void ins(int &x, int y, int z, int p, int d) {
    if(!x) {
        x = ++tot;
        son[x][0] = son[x][1] = siz[x] = rev[x] = 0;
    }
    if(y == z) { siz[x] = 1; return ; }
    pd(x, d);
    int mid = y+z>>1;
    if(p <= mid) ins(son[x][0], y, mid, p, d-1);
    else ins(son[x][1], mid+1, z, p, d-1);
}

int merge(int x, int y, int l, int r, int d) {
    if(!x || !y) return x+y;
    if(l == r) { siz[x] |= siz[y]; return x; }
    int mid = l+r>>1;
    pd(x, d); pd(y, d);
    son[x][0] = merge(son[x][0], son[y][0], l, mid, d-1);
    son[x][1] = merge(son[x][1], son[y][1], mid+1, r, d-1);
    ud(x);
    return x;  
} 

int ask(int x, int y, int z, int d) {
    if(y == z) return y;
    pd(x, d);
    int mid = y+z>>1;
    if(siz[son[x][0]] == mid-y+1) return ask(son[x][1], mid+1, z, d-1);
    else return ask(son[x][0], y, mid, d-1);
}

void dfs(int x, int f) {
    rem[x] = 0;
    for(int y, i = t[x]; i; i = e[i].frm) {
        y = e[i].to;  
        if(y != f) dfs(y, x), rem[x] ^= sg[y];
    }
    if(!a[x]) ins(rt[x], 0, N, rem[x], D);
    for(int y, i = t[x]; i; i = e[i].frm) {
        y = e[i].to;  
        if(y != f) bv(rt[y], rem[x]^sg[y], D), rt[x] = merge(rt[x], rt[y], 0, N, D);
    }
    sg[x] = ask(rt[x], 0, N, D);
}

inline void find(int x, int f) {
    if(!a[x] && !rem[x]) bl[x] = 1;
    for(int y, i = t[x]; i; i = e[i].frm) {
        y = e[i].to;
        if(y != f) rem[y] ^= rem[x]^sg[y], find(y, x);
    }
}

int main () {
    read(n);
    for(register int i = 1; i <= n; i++) read(a[i]);
    for(register int i = 1; i < n; i++) read(u), read(v), add(u, v), add(v, u);
    for(N = 1; N <= n; N <<= 1) D++;
    N--; D--;
    dfs(1, 0);
    if(!sg[1]) { printf("-1\n"); return 0; }
    find(1, 0);
    for(int i = 1; i <= n; i++) if(bl[i]) printf("%d\n",i);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值