• 【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('
    ');}
    		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;
    }
    
  • 相关阅读:
    c#中使用log4net工具记录日志
    由命名空间函数而引发思考--js中的对象赋值问题
    浅析C#中的Attribute[转]
    剑指offer_输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果
    剑指offer_输入一颗二叉树和一个整数,打印出二叉树中结点值的和为输入整数的所有路径
    剑指offer_输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字
    HashSet TreeSet 中元素顺序问题(未解决)
    找出某个String中出现次数最多的字符,并输出次数(字符较长)
    MyBatis、JDBC、Hibernate区别
    String.equals用法注意
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/BZOJ4817.html
Copyright © 2020-2023  润新知