20200718模拟赛 T2 树论【换根】

题目描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

题目分析

读错了两遍题意。。。
加棋子和换根都是对后面有影响的。换根之后子树会变化。

把棋子看做一个个独立游戏,一个点的棋子的SG值是子树最大高度(到子树中最远点的距离)。因为棋子可以放到子树中除自己外任何一个点,很容易看出来。

x x x 到以1为根时的子树中最深的点的距离为 h x h_x hx

当根从 1 换到 x x x 的子树中时, x x x 的SG值会变化,这个变化与换到了 x x x 的哪个儿子中有关,不妨重链剖分,记 c x c_x cx 为当根换到 x x x 的重儿子子树内之后 x x x 的SG值与 h x h_x hx 的异或值,可以通过求出 f x , g x f_x,g_x fx,gx x x x 到整棵树的最远距离和次远距离(不同方向),判断 f x f_x fx 是否等于 h s o n x + 1 h_{son_x}+1 hsonx+1 得出。

那么对于修改,我们维护出以 1 为根时的答案,以及重链上的修正值 ( c x c_x cx)。询问换到某个根时跳重链异或上修正值,跳轻边时单独求出修正值。最后单独求一下当前的根的修正值。

以 1 为根的答案可以通过预处理 链异或和 以及 子树异或和 O ( 1 ) O(1) O(1) 求出。
换根之后链修改照常,子树修改就是翻转一个或两个dfs序区间,树链剖分线段树维护即可。
线段树维护区间棋子为奇数的 c x c_x cx 的异或和,以及记录整个区间的 c x c_x cx 的异或和。
为了减小常数,某个点棋子是否为奇数用树状数组就可以了。(用线段树就单点查翻转标记)

预处理,树状数组的功能可以统一到线段树里面,存4个值,存在的/不存在的原异或和/修正异或和

长链剖分在求 c x c_x cx 的时候可能会方便一点,但是轻边条数可以达到 n \sqrt n n (不是每个点),随机一个点作根就不会被卡了。

标程的做法是以直径中点为根,这样换的根不在子树内时高度等于 h x h_x hx,在子树内时高度等于 f x f_x fx,轻边就不用单独算了。直径为偶数时建一个虚点。

Code:

#include<bits/stdc++.h>
#define maxn 200005
using namespace std;
char cb[1<<20],*cs,*ct;
#define getc() (cs==ct&&(ct=(cs=cb)+fread(cb,1,1<<20,stdin),cs==ct)?0:*cs++)
void read(int &a){
	char c;while(!isdigit(c=getc()));
	for(a=c-'0';isdigit(c=getc());a=a*10+c-'0');
}
int n,m,sub[maxn],chain[maxn],dep[maxn],son[maxn],top[maxn],fa[maxn],dfn[maxn],siz[maxn],ln[maxn],tim;
int f[maxn],g[maxn],h[maxn],Ans;
int fir[maxn],nxt[maxn<<1],to[maxn<<1],tot;
void line(int x,int y){nxt[++tot]=fir[x],fir[x]=tot,to[tot]=y;}
int exclude(int u,int v){return f[u]==h[v]+1?g[u]:f[u];}
void upfg(int u,int x){
	if(f[u]<x) g[u]=f[u],f[u]=x;
	else g[u]=max(g[u],x);
}
void dfs1(int u,int ff){
	dep[u]=dep[fa[u]=ff]+1,siz[u]=1;
	for(int i=fir[u],v;i;i=nxt[i]) if((v=to[i])!=ff){
		dfs1(v,u),siz[u]+=siz[v],siz[v]>siz[son[u]]&&(son[u]=v);
		h[u]=max(h[u],h[v]+1),sub[u]^=sub[v];
		upfg(u,h[v]+1);
	}
	sub[u]^=h[u];
}
void dfs2(int u,int tp){
	top[u]=tp,ln[dfn[u]=++tim]=u,chain[u]=chain[fa[u]]^h[u];
	if(fa[u]) upfg(u,exclude(fa[u],u)+1);
	if(son[u]) dfs2(son[u],tp);
	for(int i=fir[u],v;i;i=nxt[i]) if(!top[v=to[i]]) dfs2(v,v);
}
int arr[maxn];
void updarr(int i){for(;i<=n;i+=i&-i) arr[i]^=1;}
int qxor(int i){int s=0;for(;i;i-=i&-i) s^=arr[i];return s;}
int c[maxn<<2],s[maxn<<2];
bool rev[maxn<<2];
void build(int i,int l,int r){
	if(l==r) {c[i]=s[i]=exclude(ln[l],son[ln[l]])^h[ln[l]]; return;}//without son not final calc.
	int mid=(l+r)>>1;
	build(i<<1,l,mid),build(i<<1|1,mid+1,r);
	c[i]=c[i<<1]^c[i<<1|1],s[i]=s[i<<1]^s[i<<1|1];
}
void down(int i){
	if(rev[i]) c[i<<1]^=s[i<<1],rev[i<<1]^=1,c[i<<1|1]^=s[i<<1|1],rev[i<<1|1]^=1,rev[i]=0;
}
void Rev(int i,int l,int r,int x,int y){
	if(x<=l&&r<=y) {c[i]^=s[i],rev[i]^=1;return;}
	down(i);
	int mid=(l+r)>>1;
	if(x<=mid) Rev(i<<1,l,mid,x,y);
	if(y>mid) Rev(i<<1|1,mid+1,r,x,y);
	c[i]=c[i<<1]^c[i<<1|1]^(rev[i]?s[i]:0);
}
int qry(int i,int l,int r,int x,int y){
	if(x<=l&&r<=y) return c[i];
	down(i);
	int mid=(l+r)>>1,ret=0;
	if(x<=mid) ret^=qry(i<<1,l,mid,x,y);
	if(y>mid) ret^=qry(i<<1|1,mid+1,r,x,y);
	return ret;
}
void change(int u,int v){
	for(;top[u]!=top[v];u=fa[top[u]]){
		if(dep[top[u]]<dep[top[v]]) swap(u,v);
		updarr(dfn[top[u]]),updarr(dfn[u]+1);
		Rev(1,1,n,dfn[top[u]],dfn[u]);
	}
	if(dep[u]<dep[v]) swap(u,v);
	updarr(dfn[v]),updarr(dfn[u]+1);
	Rev(1,1,n,dfn[v],dfn[u]);
	Ans^=h[v]; //v is LCA.
}
int find(int x,int t){
	for(;top[x]!=top[t];x=fa[x]) if(fa[x=top[x]]==t) return x;
	return son[t];
}
void subtree(int x){
	Ans^=sub[x];
	updarr(dfn[x]),updarr(dfn[x]+siz[x]);
	Rev(1,1,n,dfn[x],dfn[x]+siz[x]-1);
}
int solve(int x){
	int ret = qxor(dfn[x])*(f[x]^h[x]);
	for(int t;x;){
		if((t=top[x])!=x) ret^=qry(1,1,n,dfn[t],dfn[x]-1);
		if(qxor(dfn[x=fa[t]])) ret^=exclude(x,t)^h[x];
	}
	return ret;
}
int main()
{
	freopen("tree.in","r",stdin);
	freopen("tree.out","w",stdout);
	read(n),read(m);
	for(int i=1,x,y;i<n;i++) read(x),read(y),line(x,y),line(y,x);
	dfs1(1,0),dfs2(1,1);
	updarr(1),Ans=sub[1],build(1,1,n);
	for(int op,x,y,r=1;m--;){
		read(op),read(x);
		if(op==1) read(y),Ans^=chain[x]^chain[y],change(x,y);
		else{
			if(x==r) subtree(1);
			else if(dfn[x]<=dfn[r]&&dfn[r]<dfn[x]+siz[x]) subtree(1),subtree(find(r,x));
			else subtree(x);
		}
		read(r);
		printf("%d\n",Ans^solve(r));
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值