zoukankan      html  css  js  c++  java
  • 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;
    }
    

  • 相关阅读:
    CF1029C Maximal Intersection 暴力枚举
    2018中国大学生程序设计竞赛
    php远程抓取(下载)文件到本项目指定目录中
    FCKEditor添加字体
    UCenter 与 DIscuz 通信失败的解决方法
    运用php函数mkdir创建多级目录
    PHP 数组函数-数组排序
    php登陆ssh执行命令
    php定时执行任务的几个方法
    PHP Yii框架中使用smarty模板
  • 原文地址:https://www.cnblogs.com/Troverld/p/14602138.html
Copyright © 2011-2022 走看看