uoj719. 【北大集训2021】经典游戏
首先每颗棋子是独立的,找到他们的 sg 异或起来看一下是不是 0 即可。(所以我们只关心每个点上棋子数的奇偶。)
对于一个树根确定的情况,很容易发现一个节点的 sg 就是它到它子树内最远点的距离。但是这个东西不好直接维护,但是如果不管根,离这个点最远的点肯定是树的直径的两个端点之一,所以我们可以把树看成这样的一个结构:
在这个结构中,离左子树最远的点肯定是处于右子树的那个直径端点,右子树同理,所以对于一个点,如果目前树根在它的子树内,那离他最远的点肯定是另一颗子树的那个直径端点,如果树根不在它子树内,离他最远的点就在它子树内,这可以先跑一遍树形 DP 处理出来。
接下来要处理的是加入一个棋子,考虑它对各个点为根的影响,像上面一样分根在不在它子树内讨论就行了,是一个子树修改,树状数组维护即可。
然后是后手会新加一个棋子,如果我们知道根,和目前的 sg 值的异或和,后手的目的是使 sg 的异或和变成 0,显然放一个棋子会给 sg 值异或上一个 \(1\) 到 \(x\) 的数,其中 \(x\) 为根到离他最远的点的距离,所以我们需要判断的是 \(x\) 与目前的 sg 值得大小关系。
之后是先手选择换根的问题,先手其实是想让换根后的 sg 大于 \(x\),但是与题目给定的 \(y\) 相邻的点的 \(x\) 并不是完全一样,不好处理,但是发现除了 \(y\) 本身和 \(y\) 的父亲是特殊的之外,\(y\) 的儿子的 \(x\) 都是一样的,所以考虑把儿子一起处理。
现在我们需要维护儿子的 sg 值的集合,发现对于加入一个在 \(u\) 的棋子的操作,发现对于除了 \(u\) 的父亲以外的点,对于他们的儿子异或上的数都是一样的,所以我们可以只打标记,而这个打标记也可以树状数组,现在问题变成对一个点的儿子集合求 \(\sum sg_i\ xor\ A>x\),这是一个经典的用 trie 解决的问题。
还有就是对于 \(u\) 的父亲这里它的儿子 \(u\) 异或上的数和其他儿子不一样,把 \(u\) 暴力删除然后重新插入trie 即可。
#include<iostream>
#include<stdio.h>
#include<ctype.h>
#include<vector>
#include<string.h>
#include<queue>
#define N 1000005
#define lowbit(x) (x&-x)
using namespace std;
inline int read(){
int x=0,f=0; char ch=getchar();
while(!isdigit(ch)) f|=(ch==45),ch=getchar();
while(isdigit(ch)) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
return f?-x:x;
}
int n,m,dis[N],pre[N],s1,s2,r1,r2,dfn[N],sze[N],to[N],dis1[N],dis2[N],c[2][N],ff[N],pool,son[40*N][2],Sze[40*N],bel[N];
vector<int> G[N];
queue<int> q;
inline void bfs(int S){
memset(dis,0x3f,sizeof(dis));
dis[S]=0,q.push(S);
while(!q.empty()){
int u=q.front();q.pop();
for(int v:G[u]){
if(dis[u]+1<dis[v]){
dis[v]=dis[u]+1;
pre[v]=u,q.push(v);
}
}
}
}
void dfs1(int u,int fa){
dfn[u]=++dfn[0],sze[u]=1;
ff[u]=fa;
for(int v:G[u]){
if(v==fa) continue;
dfs1(v,u);
sze[u]+=sze[v];
to[u]=max(to[u],to[v]+1);
}
}
void dfs2(int u,int fa,int o){
for(int v:G[u]){
if(v==fa) continue;
if(o==1) dis1[v]=dis1[u]+1;
else dis2[v]=dis2[u]+1;
dfs2(v,u,o);
}
}
inline void update(int op,int x,int v){
for(int i=x;i<=n;i+=lowbit(i)) c[op][i]^=v;
}
inline int query(int op,int x){
int res=0;
for(int i=x;i;i-=lowbit(i)) res^=c[op][i];
return res;
}
inline void insert(int RT,int x,int v){
int p=RT;Sze[p]+=v;
for(int i=19;~i;--i){
if(!son[p][(x>>i)&1]) son[p][(x>>i)&1]=++pool;
p=son[p][(x>>i)&1];
Sze[p]+=v;
}
}
inline int ask(int RT,int buf,int k){
int p=RT,res=0;
for(int i=19;~i;--i){
if((k>>i)&1) p=son[p][((buf>>i)&1)^1];
else res+=Sze[son[p][((buf>>i)&1)^1]],p=son[p][(buf>>i)&1];
if(!p) break;
}
return res;
}
inline void modify(int u){
int d=max(dis1[u],dis2[u]);
update(0,1,to[u]);
update(0,dfn[u],d^to[u]);
update(0,dfn[u]+sze[u],d^to[u]);
if(u!=r1 && u!=r2){
int tmp=query(1,dfn[u]);
insert(ff[u],bel[u],-1);
bel[u]=tmp^d^query(0,dfn[ff[u]]);
insert(ff[u],bel[u],1);
}
update(1,1,to[u]);
update(1,dfn[u],d^to[u]);
update(1,dfn[u]+sze[u],d^to[u]);
}
int main(){
read();
n=read(),m=read();
for(int i=1;i<n;++i){
int x=read(),y=read();
G[x].push_back(y);
G[y].push_back(x);
}
bfs(1);
dis[0]=-1;
for(int i=1;i<=n;++i) if(dis[i]>dis[s1]) s1=i;
bfs(s1);
dis[0]=-1;
for(int i=1;i<=n;++i) if(dis[i]>dis[s2]) s2=i;
int now=s2,cnt=0;
while(now!=s1) cnt++,now=pre[now];
cnt/=2;
now=s2;
for(int i=1;i<=cnt;++i) now=pre[now];
r2=now,r1=pre[now];
dfs1(r1,r2),dfs1(r2,r1);
dfs2(s1,0,1),dfs2(s2,0,2);
pool=n;
for(int i=1;i<=n;++i) if(G[i].size()!=1) insert(i,0,G[i].size()-1);
for(int i=1;i<=n;++i){
int x=read()%2;
if(!x) continue;
modify(i);
}
while(m--){
int x=read(),y=read();
modify(x);
int res=0;
res+=(max(dis1[y],dis2[y])<query(1,dfn[y]));
res+=(max(dis1[ff[y]],dis2[ff[y]])<query(1,dfn[ff[y]]));
res+=ask(y,query(0,dfn[y]),max(dis1[y],dis2[y])+1);
printf("%d\n",res);
}
return 0;
}