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