开始搬神犇的题解
Orz
题意
给定一棵 N 个点的树, 1 号点为根,每个节点是白色或者黑色。
双方轮流操作,每次选择一个白色节点,将从这个点到根的路径上的点全部染成黑色。
问先手是否必胜,以及第一步可选节点有哪些。
N<=100000分析
首先是博弈方面的分析。
令 SG[x] 为,只考虑以 x 为根的子树时的 SG 值。
令 g[x] 为,只考虑以 x 为根的子树时,所有后继局面的 SG 值的集合。那么 SG[x]=mex{g[x]} 。
我们考虑怎么计算 g[x] 。假设x的儿子为 v1,v2,...,vk ,令 sum[x]=SG[v1] xor SG[v2] xor ⋯ xorSG[vk]
考虑两种情况:
x 为黑色 不难发现以 x 的每个儿子为根的子树是互相独立的。假设这一步选择了 vi 子树的某一个节点,那么转移到的局面的 SG 值就是 sum[x] xor SG[vi] xor g[vi][j] 。那么我们只需将每个 g[vi] 整体xor上 sum[x]xorSG[vi] 再合并到g[x]即可。
x为白色。这时候我们多了一种选择,即选择 x 点。可以发现,选择x点之后 x 点变成黑色,所有子树仍然独立,而转移到的局面的SG值就是 sum[x] 。如果此时不选择 x 而是选择 x 子树里的某个白色节点,那么 x 一样会被染成黑色,所有子树依然独立。所以 x 为白色时只是要向 g[x] 中多插入一个值 sum[x] 。
接下来再考虑第一步可选的节点。我们要考虑选择哪些节点之后整个局面的 SG 值会变成 0 。
假设我们选择了 x 点,那么从x到根的路径都会被染黑,将原来的树分成了一堆森林。
我们令 up[x] 为,不考虑以 x 为根的子树,将从 x 到根的路径染黑,剩下的子树的 SG 值的xor和。
那么 up[x]=up[fa[x]] xor sum[fa[x]] xor sg[x] ,其中 fa[x] 为 x 的父亲节点编号。
那么如果点 x 初始颜色为白色且 up[x]xorsum[x]=0 ,那么这个点就是第一步可选的节点。
这一步是 O(N) 的。
#include<cstdio>
#include<cstdlib>
#include<algorithm>
using namespace std;
inline char nc(){
static char buf[100000],*p1=buf,*p2=buf;
return p1==p2&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++;
}
inline void read(int &x){
char c=nc(),b=1;
for (;!(c>='0' && c<='9');c=nc()) if (c=='-') b=-1;
for (x=0;c>='0' && c<='9';x=x*10+c-'0',c=nc()); x*=b;
}
const int M=5000005;
const int N=100005;
const int K=31;
int ncnt;
int rt[N];
int ls[M],rs[M],fl[M],tg[M],d[M];
inline int bit(int x,int d){
return x&(1<<(K-d-1));
}
inline void mark(int x,int t){
if (!x) return;
tg[x]^=t;
if (bit(t,d[x]))
swap(ls[x],rs[x]);
}
inline void upd(int x){
fl[x]=fl[ls[x]]&&fl[rs[x]];
}
inline void push(int x){
if (!tg[x]) return;
mark(ls[x],tg[x]);
mark(rs[x],tg[x]);
tg[x]=0;
}
inline void ins(int &x,int num,int dep){
if (!x) x=++ncnt,d[x]=dep;
push(x);
if (dep==K) { fl[x]=1; return; }
if (bit(num,dep))
ins(rs[x],num,dep+1);
else
ins(ls[x],num,dep+1);
upd(x);
}
inline int mex(int x){
if (!x || d[x]==K) return 0;
int ret=0;
if (fl[ls[x]])
return (1<<(K-d[x]-1))+mex(rs[x]);
else
return mex(ls[x]);
}
inline void merge(int &x,int y){
if (!x||!y) return void(x=x+y);
if (d[x]==K) return void(fl[x]=fl[x]||fl[y]);
push(x); push(y);
merge(ls[x],ls[y]);
merge(rs[x],rs[y]);
upd(x);
}
struct edge{
int u,v,next;
}G[N<<1];
int head[N],inum;
inline void add(int u,int v,int p){
G[p].u=u; G[p].v=v; G[p].next=head[u]; head[u]=p;
}
#define V G[p].v
int n;
int val[N];
int sg[N],sum[N];
inline void dfs(int u,int fa){
sum[u]=0;
for (int p=head[u];p;p=G[p].next)
if (V!=fa)
dfs(V,u),sum[u]^=sg[V];
for (int p=head[u];p;p=G[p].next)
if (V!=fa){
mark(rt[V],sum[u]^sg[V]);
merge(rt[u],rt[V]);
}
if (!val[u])
ins(rt[u],sum[u],1);
sg[u]=mex(rt[u]);
}
int Ans[N],Pnt;
int f[N];
inline void find(int u,int fa){
if (fa) f[u]=f[fa]^sum[fa]^sg[u];
for (int p=head[u];p;p=G[p].next)
if (V!=fa)
find(V,u);
if (!val[u] && (f[u]^sum[u])==0)
Ans[++Pnt]=u;
}
int main(){
int iu,iv;
freopen("t.in","r",stdin);
freopen("t.out","w",stdout);
read(n);
for (int i=1;i<=n;i++) read(val[i]);
for (int i=1;i<n;i++) read(iu),read(iv),add(iu,iv,++inum),add(iv,iu,++inum);
dfs(1,0);
find(1,0);
if (!Pnt)
printf("-1\n");
else{
sort(Ans+1,Ans+Pnt+1);
for (int i=1;i<=Pnt;i++)
printf("%d\n",Ans[i]);
}
return 0;
}