[SG函数 Trie树合并] SPOJ COT3 Combat on a tree

开始搬神犇的题解
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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值