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;
}

posted @ 2022-06-13 10:16  CHiSwsz  阅读(79)  评论(0编辑  收藏  举报