把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【BZOJ4817】[SDOI2017] 树点涂色(LCT的Access又一次妙用)

点此看题面

大致题意: 给定一棵有根树,一开始每个点颜色各不相同,定义一条路径权值为路径上的颜色数。支持三种操作:把一个点到根路径上所有点染成同种新颜色;求一条树上路径的权值;在某一子树中选一个点,求这个点到根路径权值的最大值。

前言

这差不多可以算作我第一次写线段树标记永久化,结果\(PushUp\)时忘记加上标记,调了半个多小时。。。

如果是想练\(LCT\),最好不要来做这道题,因为这道题\(LCT\)的分量还没有树剖+线段树的一半。。。

\(Access\)

考虑题目中把一个点到根路径上所有点染成同种颜色。

一个点到根路径,想到什么?这不就是\(Access\)嘛!

于是,我们只要不断地\(Access\),就可以实现用\(LCT\)上的每一棵\(Splay\)维护一条同色链,且由于每次染的是新颜色,可以保证所有同色的点都在同一棵\(Splay\)中。

关于颜色数

颜色数向来是超级难维护的东西之一。。。

但在这道题中有个特殊的性质,即同种颜色必然是一条笔直的链(不然我们怎么能用\(Splay\)维护呢)。

这样一来,就可以树上差分了。

我们设\(Val_i\)\(i\)到根路径上的颜色数,则\(x,y\)路径的权值就是\(Val_x+Val_y-2Val_{LCA(x,y)}+1\)。注意此处的\(+1\),因为\(LCA(x,y)\)的贡献是需要被计算在内的。

信息维护

然后我们考虑怎么维护\(Val_i\)

考虑在\(Access\)过程中,每次我们会用当前的儿子替换掉之前的儿子。

对于之前的儿子,相当于它所在的子树(注意,不是以它为根,根需要我们在\(Splay\)中找),到根节点路径上都多了一种颜色,\(Val\)全加\(1\)

对于当前的儿子,相当于它所在的子树到根节点的路径上都少了一种颜色,\(Val\)全减\(1\)

再考虑询问是单点询问以及子树求最大值。

看到操作对象只有单点和子树,容易想到\(dfs\)序(这样单点仍是单点,子树却成了一个区间),然后线段树维护。

于是这道题就做完了。

代码

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100000
#define add(x,y) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y)
#define Gmax(x,y) (x<(y)&&(x=(y)))
#define swap(x,y) (x^=y^=x^=y)
using namespace std;
int n,ee,lnk[N+5];struct edge {int to,nxt;}e[N<<1];
class FastIO
{
	private:
		#define FS 100000
		#define tc() (A==B&&(B=(A=FI)+fread(FI,1,FS,stdin),A==B)?EOF:*A++)
		#define pc(c) (C==E&&(clear(),0),*C++=c)
		#define tn (x<<3)+(x<<1)
		#define D isdigit(c=tc())
		int T;char c,*A,*B,*C,*E,FI[FS],FO[FS],S[FS];
	public:
		I FastIO() {A=B=FI,C=FO,E=FO+FS;}
		Tp I void read(Ty& x) {x=0;W(!D);W(x=tn+(c&15),D);}
		Tp I void write(Ty x) {W(S[++T]=x%10+48,x/=10);W(T) pc(S[T--]);}
		Tp I void writeln(Con Ty& x) {write(x),pc('\n');}
		I void clear() {fwrite(FO,1,C-FO,stdout),C=FO;}
}F;
namespace TreeChainDissection//树链剖分(你问哪来的树剖?反正都写dfs序了,干脆写树剖求LCA呗)
{
	int d,v[N+5],dfn[N+5],sz[N+5],fa[N+5],son[N+5],dep[N+5],tp[N+5];
	class SegmentTree//线段树(试着写了写标记永久化)
	{
		private:
			#define PT CI l=1,CI r=n,CI rt=1
			#define LT l,mid,rt<<1
			#define RT mid+1,r,rt<<1|1
			#define PU(x) (Mx[x]=max(Mx[x<<1],Mx[x<<1|1])+F[x])
			int Mx[N<<2],F[N<<2];
		public:
			I void Build(PT)//建树
			{
				if(l==r) return (void)(Mx[rt]=v[l]);int mid=l+r>>1;
				Build(LT),Build(RT),PU(rt);
			}
			I void U(CI x,CI y,CI v,PT)//区间修改
			{
				if(x<=l&&r<=y) return (void)(Mx[rt]+=v,F[rt]+=v);int mid=l+r>>1;
				x<=mid&&(U(x,y,v,LT),0),y>mid&&(U(x,y,v,RT),0),PU(rt);
			}
			I int Q(CI x,CI y,PT)//区间求最大值
			{
				if(x<=l&&r<=y) return Mx[rt];int mid=l+r>>1,p=0,t;
				x<=mid&&(t=Q(x,y,LT),Gmax(p,t)),y>mid&&(t=Q(x,y,RT),Gmax(p,t));
				return p+F[rt];
			}
	}T;
	I void dfs1(CI x=1)//树剖第一次DFS
	{
		sz[x]=1;for(RI i=lnk[x];i;i=e[i].nxt) e[i].to^fa[x]&&
		(
			dep[e[i].to]=dep[fa[e[i].to]=x]+1,dfs1(e[i].to),
			sz[x]+=sz[e[i].to],sz[e[i].to]>sz[son[x]]&&(son[x]=e[i].to)
		);
	}
	I void dfs2(CI x=1,CI t=1)//树剖第二次DFS
	{
		dfn[x]=++d,tp[x]=t,son[x]&&(dfs2(son[x],t),0);
		for(RI i=lnk[x];i;i=e[i].nxt) e[i].to^fa[x]&&e[i].to^son[x]&&(dfs2(e[i].to,e[i].to),0);
	}
	I void Init() {dep[1]=1,dfs1(),dfs2();for(RI i=1;i<=n;++i) v[dfn[i]]=dep[i];T.Build();}//注意每个点初始值为深度
	I int LCA(RI x,RI y)//树剖求LCA
	{
		W(tp[x]^tp[y]) dep[tp[x]]>dep[tp[y]]?x=fa[tp[x]]:y=fa[tp[y]];return dfn[x]<dfn[y]?x:y;
	}
}using namespace TreeChainDissection;
class LinkCutTree//简约版本的LCT
{
	private:
		#define IR(x) (O[O[x].F].S[0]^x&&O[O[x].F].S[1]^x)
		#define Wh(x) (O[O[x].F].S[1]==x)
		#define Co(x,y,d) (O[O[x].F=y].S[d]=x)
		struct {int F,S[2];}O[N+5];
		I void Ro(RI x)
		{
			RI f=O[x].F,p=O[f].F,d=Wh(x);!IR(f)&&(O[p].S[Wh(f)]=x),
			O[x].F=p,Co(O[x].S[d^1],f,d),Co(f,x,d^1);
		}
		I void S(CI x) {RI f;W(!IR(x)) f=O[x].F,!IR(f)&&(Ro(Wh(x)^Wh(f)?x:f),0),Ro(x);}
		I int FR(RI x) {W(O[x].S[0]) x=O[x].S[0];return x;}//注意此处是在Access过程中找根,故有所不同
	public:
		I void Link(RI x,RI y) {S(y),O[y].F=x;//连边
		I void Ac(RI x)//特殊版本Access
		{
			for(RI y=0,t;x;x=O[y=x].F) S(x),
				O[x].S[1]&&(t=FR(O[x].S[1]),T.U(dfn[t],dfn[t]+sz[t]-1,1),0),//旧儿子加1
				y&&(t=FR(y),T.U(dfn[t],dfn[t]+sz[t]-1,-1),0),O[x].S[1]=y;//新儿子减1
		}
}LCT;
I void dfs(CI x,CI lst=0)//dfs给LCT连边
{
	for(RI i=lnk[x];i;i=e[i].nxt)
		e[i].to^lst&&(LCT.Link(x,e[i].to),dfs(e[i].to,x),0);
}
int main()
{
	RI Qt,i,op,x,y,z;F.read(n),F.read(Qt);
	for(i=1;i^n;++i) F.read(x),F.read(y),add(x,y),add(y,x);dfs(1),Init();
	W(Qt--) switch(F.read(op),F.read(x),op)//处理询问
	{
		case 1:LCT.Ac(x);break;
		case 2:F.read(y),z=LCA(x,y),
			F.writeln(T.Q(dfn[x],dfn[x])+T.Q(dfn[y],dfn[y])-2*T.Q(dfn[z],dfn[z])+1);break;
		case 3:F.writeln(T.Q(dfn[x],dfn[x]+sz[x]-1));break;
	}return F.clear(),0;
}
posted @ 2020-05-13 13:03  TheLostWeak  阅读(202)  评论(0编辑  收藏  举报