SP16549 QTREE6 Query on a tree VI

XX.SP16549 QTREE6 - Query on a tree VI

本题LCT全方面爆破树剖——无论是复杂度、码量、思维难度、代码难度,全都碾压树剖。并且,LCT容易模板化,但是树剖不容易——树剖搭配的线段树因题而异,而LCT只需要把\(pushup\)\(pushdown\)稍微改改就可以直接粘模板了……

看一下对比吧。

所以,能写LCT,最好不要写树剖……

闲话少说,我们分析一下两种方法各应该怎么写。

LCT的话,我们照例可以跟XII.[ZJOI2012]网络一样,对每种颜色建一棵LCT,并且这题还只有两种颜色黑和白。然后LCT维护虚子树大小,每次就是查询某个点在对应图中的树的大小。看上去就是[ZJOI2012]网络+[BJOI2014]大融合,不至于到黑题呀!

等等,[ZJOI2012]网络是边带色,但这题是点带色!暴力断边的话,如果给你来一张菊花图分分钟爆炸。

怎么办呢?

在前几题中,我们有将LCT边转点的经历——因为LCT只能维护点权而不能维护边权。但是,因为LCT长于断边而短于断点,我们这次点转边

操作很naive。从\(1\)号点开始出发爆搜,每个点的点权转到于它父亲连着的边上。如果一条边为黑,在\(0\)号LCT上连上这条边;否则,在\(1\)号LCT上连上这条边。为了维护\(1\)号点的点权,我们不妨设\(1\)的父亲为\(n+1\)

之后,在一个点换颜色时,直接一断一连就行了。并且,因为操作的特殊性,我们这题甚至不需要\(makeroot\),也就是说,不需要区间翻转!这使得就连LCT套treap这种稀奇古怪的东西也可以跑过这道题

当然,这么点边一转,直接输出连通块大小肯定出问题(不信手玩样例一下)。

在统计答案时,我们考虑\(findroot\)找出\(x\)的树的\(ROOT\)。如果\(ROOT\)\(x\)颜色相同,那么这个连通块大小就没有问题,可以直接输出。

那么如果\(ROOT\)\(x\)颜色不同呢?在\(findroot\)后,\(ROOT\)同时也是\(root\),并且\(ROOT\)\(x\)还在同一棵splay里面。因为\(ROOT\)的深度是最小的,因此它的右儿子的\(size\)即是\(x\)的连通块的大小。

代码(\(57\)行):

#include<bits/stdc++.h>
using namespace std;
int n,m,head[100100],cnt,pa[100100],col[100100];
struct Link_Cut_Tree{
	#define lson ch[0][x]
	#define rson ch[1][x]
	int fa[100100],ch[2][100100],si[100100],sr[100100],self[100100];
	inline int identify(int x){
		if(x==ch[0][fa[x]])return 0;
		if(x==ch[1][fa[x]])return 1;
		return -1;
	}
	inline void pushup(int x){sr[x]=sr[lson]+sr[rson]+si[x]+1;}
	inline void rotate(int x){
		register int y=fa[x],z=fa[y],dirx=identify(x),diry=identify(y),b=ch[!dirx][x];
		if(diry!=-1)ch[diry][z]=x;fa[x]=z;
		if(b)fa[b]=y;ch[dirx][y]=b;
		fa[y]=x,ch[!dirx][x]=y;
		pushup(y),pushup(x);
	}
	inline void splay(int x){for(;identify(x)!=-1;rotate(x))if(identify(fa[x])!=-1)rotate(identify(x)==identify(fa[x])?fa[x]:x);pushup(x);}
	inline void access(int x){for(register int y=0;x;x=fa[y=x])splay(x),si[x]+=sr[rson]-sr[y],rson=y,pushup(x);}
	inline int findroot(int x){access(x),splay(x);while(lson)x=lson;splay(x);return x;}
	inline void link(int x){splay(x);int y=fa[x]=pa[x];access(y),splay(y),si[y]+=sr[x],pushup(y);}
	inline void cut(int x){access(x),splay(x),lson=fa[lson]=0,pushup(x);}
}lct[2];
struct Edge{
	int to,next;
}edge[200100];
void ae(int u,int v){
	edge[cnt].next=head[u],edge[cnt].to=v,head[u]=cnt++;
	edge[cnt].next=head[v],edge[cnt].to=u,head[v]=cnt++;
}
void dfs(int x,int fa){
	pa[x]=fa,lct[0].link(x);
	for(int i=head[x];i!=-1;i=edge[i].next)if(edge[i].to!=fa)dfs(edge[i].to,x);
}
int A(int x){
	int y=lct[col[x]].findroot(x);
	return lct[col[x]].sr[col[y]==col[x]?y:lct[col[x]].ch[1][y]];
}
void R(int x){
	lct[col[x]].cut(x),lct[!col[x]].link(x),col[x]^=1;
}
int main(){
	scanf("%d",&n),memset(head,-1,sizeof(head));
	for(int i=1,x,y;i<n;i++)scanf("%d%d",&x,&y),ae(x,y);
	col[n+1]=2;
	dfs(1,n+1);
	scanf("%d",&m);
	for(int i=1,x,y;i<=m;i++){
		scanf("%d%d",&x,&y);
		if(!x)printf("%d\n",A(y));
		else R(y);
	}
	return 0;
}

然后就是树剖了。

树剖这个idea十分诡异:设\(black_x\)表示:如果\(x\)是黑的(不管它真正的颜色是黑是白),在它的子树内黑色连通块的大小。再设\(white_x\)表示:如果\(x\)是白的(不管它真正的颜色是黑是白),在它的子树内白色连通块的大小。

\(x\)的答案为:沿着\(x\)\(1\)的路径一直往上爬,直到某个点的父亲的颜色和\(x\)的颜色不同(如果不存在这样的点,默认是\(1\)号节点),则答案就是这个点对应的\(black\)\(white\)值。

那么如果一个点的颜色翻转一下,会怎么办呢?

我们假设是黑转白,因为白转黑也是同理。

我们找到\(fa_x\)到根的路径上第一个黑节点\(u\),则路径\((fa_x,u)\)上每一个点的\(black\)全都应该减去\(black_x\)

我们找到\(fa_x\)到根的路径上第一个白节点\(v\),则路径\((fa_x,v)\)上每一个点的\(white\)全都应该加上\(white_x\)

思想还比较容易,写起来非常恶心。

代码:

#include<bits/stdc++.h>
using namespace std;
#define lson x<<1
#define rson x<<1|1
#define mid ((l+r)>>1)
int n,m,dfn[100100],rev[100100],fa[100100],dep[100100],son[100100],top[100100],sz[100100],head[100100],cnt,tot,col[100100];
struct node{
	int to,next;
}edge[200100];
void ae(int u,int v){
	edge[cnt].next=head[u],edge[cnt].to=v,head[u]=cnt++;
	edge[cnt].next=head[v],edge[cnt].to=u,head[v]=cnt++;
}
void dfs1(int x,int Fa){
	fa[x]=Fa,dep[x]=dep[Fa]+1,sz[x]=1;
	for(int i=head[x],y;i!=-1;i=edge[i].next){
		if((y=edge[i].to)==fa[x])continue;
		dfs1(y,x),sz[x]+=sz[y];
		if(sz[son[x]]<sz[y])son[x]=y;
	}
}
void dfs2(int x){
	if(son[x])top[son[x]]=top[x],dfn[son[x]]=++tot,rev[tot]=son[x],dfs2(son[x]);
	for(int i=head[x],y;i!=-1;i=edge[i].next){
		y=edge[i].to;
		if(y==fa[x]||y==son[x])continue;
		top[y]=y,dfn[y]=++tot,rev[tot]=y,dfs2(y);
	}
}
struct STA{
	int mx[2][400100];
	void pushup(int x){
		mx[0][x]=max(mx[0][lson],mx[0][rson]);
		mx[1][x]=max(mx[1][lson],mx[1][rson]);
	}
	void build(int x,int l,int r){
		if(l==r){mx[0][x]=l,mx[1][x]=0;return;}
		build(lson,l,mid),build(rson,mid+1,r),pushup(x);
	}
	void rever(int x,int l,int r,int P){
		if(l>P||r<P)return;
		if(l==r){swap(mx[0][x],mx[1][x]);return;}
		rever(lson,l,mid,P),rever(rson,mid+1,r,P),pushup(x);
	}
	int query(int x,int l,int r,int L,int R,int tp){
		if(l>R||r<L)return 0;
		if(L<=l&&r<=R)return mx[tp][x];
		return max(query(lson,l,mid,L,R,tp),query(rson,mid+1,r,L,R,tp));
	}
}sa;
struct STB{
	int val[100100],tag[400100];
	void ADD(int x,int l,int r,int w){
		if(l==r)val[l]+=w;
		else tag[x]+=w;
	}
	void pushdown(int x,int l,int r){
		ADD(lson,l,mid,tag[x]);
		ADD(rson,mid+1,r,tag[x]);
		tag[x]=0;
	}
	void modify(int x,int l,int r,int L,int R,int w){
		if(l>R||r<L)return;
		if(L<=l&&r<=R){ADD(x,l,r,w);return;}
		pushdown(x,l,r),modify(lson,l,mid,L,R,w),modify(rson,mid+1,r,L,R,w);
	}
	int query(int x,int l,int r,int P){
		if(l>P||r<P)return 0;
		if(l==r)return val[P];
		pushdown(x,l,r);
		return query(lson,l,mid,P)+query(rson,mid+1,r,P);
	}
}sb[2];
int gettop(int x,int tp){
	int res=0;
	while(x)res=max(res,sa.query(1,1,n,dfn[top[x]],dfn[x],tp)),x=fa[top[x]];
	return res;
}
int GETTOP(int x,int tp){
	int res=0;
	while(x){
		res=max(res,sa.query(1,1,n,dfn[top[x]],dfn[x],tp));
//		printf("(%d,%d):%d\n",x,top[x],res);
		if(res)return res+1;
		if(col[fa[top[x]]]==tp)return dfn[top[x]];
		x=fa[top[x]];
	}
	return 1;
}
int calc(int x){
	int tmp=GETTOP(x,!col[x]);
//	printf("%d\n",rev[tmp]);
	return sb[col[x]].query(1,1,n,tmp);
}
void modiline(int x,int y,int z,int tp){
	while(top[x]!=top[y]){
		if(dep[x]<dep[y])swap(x,y);
		sb[tp].modify(1,1,n,dfn[top[x]],dfn[x],z);
		x=fa[top[x]];
	}
	if(dep[x]>dep[y])swap(x,y);
	sb[tp].modify(1,1,n,dfn[x],dfn[y],z);
}
void REVER(int x){
	if(x==1){sa.rever(1,1,n,dfn[x]),col[x]^=1;return;}
	int tmp1,tmp2;
	
	tmp1=gettop(fa[x],col[x]),tmp2=sb[!col[x]].query(1,1,n,dfn[x]);
	if(!tmp1)tmp1++;
	tmp1=rev[tmp1];
	modiline(fa[x],tmp1,tmp2,!col[x]);
	
	tmp1=gettop(fa[x],!col[x]),tmp2=sb[col[x]].query(1,1,n,dfn[x]);
	if(!tmp1)tmp1++;
	tmp1=rev[tmp1];
	modiline(fa[x],tmp1,-tmp2,col[x]);
	
	sa.rever(1,1,n,dfn[x]),col[x]^=1;
}
int main(){
	scanf("%d",&n),memset(head,-1,sizeof(head));
	for(int i=1,x,y;i<n;i++)scanf("%d%d",&x,&y),ae(x,y);
	dfs1(1,0),rev[1]=top[1]=dfn[1]=tot=1,dfs2(1),sa.build(1,1,n);
	col[0]=2;
//	for(int i=1;i<=n;i++)printf("%d ",dfn[i]);puts("");
	for(int i=1;i<=n;i++)sb[0].val[dfn[i]]=sz[i],sb[1].val[dfn[i]]=1;
	scanf("%d",&m);
	for(int i=1,x,y;i<=m;i++){
		scanf("%d%d",&x,&y);
		if(!x)printf("%d\n",calc(y));
		else REVER(y);
	}
	return 0;
}

原文地址:https://www.cnblogs.com/Troverld/p/14602138.html