【BZOJ4573】[ZJOI2016] 大森林(LCT)
大致题意: 有\(n\)棵树,初始各有\(1\)个编号为\(1\)的节点,且其为生长节点。\(3\)种操作:将\([l,r]\) 区间内的树增加一个新的编号的节点,修改 \([l,r]\)区间内的树生长节点(没有该节点的树忽略此操作),询问某一棵树上两点的距离(保证存在)。
考虑离线
不难发现,无论什么操作,都不会改变原有树的形态,因此,询问的答案是不会变的。
因此,就不难想到离线,从\(1\)到\(n\)枚举每棵树,每次只考虑相邻两棵树之间的差异,然后修改并进行询问。
这也是做这道题的一个基础思想。
而要进行修改,就需要使用\(LCT\)。
又由于这题树的形态固定,因此要用不换根\(LCT\)。
考虑增加节点操作
接下来我们考虑增加节点操作。
不难发现,其实我们把对\([l,r]\)增加节点改成对\([1,n]\)增加节点,其实对答案不会有任何影响。
因为你增加节点与询问时,都不可能对这个并不实际存在的点进行操作,因此这个点即使存在,也是完全多余的,不会造成任何影响。
但是要注意题目中的提醒,在修改生长节点时一定不能修改没有这个点的树。
这其实也只要记一下每个点实际存在的区间,修改时将修改左边界与实际存在的左边界取\(max\),修改右边界与实际存在的右边界取\(min\)即可。
考虑询问操作
暂时先不考虑修改生长节点的操作,我们先考虑之前提到的,在知道当前树的情况下,如何求两点距离,即如何处理询问。
这时就要用到一个神奇的技巧:\(Access\)求\(LCA\)。
假设要在\(LCT\)中求\(x,y\)的\(LCA\),则我们先\(Access(x)\),此时\(x\)到根的路径上的边都变成了实边。
然后\(Access(y)\),此时\(y\)到根的路径上的边都变成了实边。
而\(LCA(x,y)\),不难发现应该是在两次操作中,都可以从根出发只走实边到达的深度最大的节点。
这其实就是我们在\(Access(y)\)时最后操作的节点!(具体实现可以见代码)
而求出了\(LCA\),只要用\(Depth_x+Depth_y\)减去\(2Depth_{LCA}\)即可。
但\(Depth\)怎么求呢?
我们可以对于\(LCT\)的\(Splay\)中的每个点,记录下它子树内实节点的个数(之所以要说实节点,是因为为了处理修改生长节点的操作,我们需要增加虚节点,这在后文有详细解释)。
而在\(Access\)并\(Splay\)了\(x/y/LCA\)之后,此时以其为根的\(Splay\)维护的就是从根节点到其路径上的信息,而此时\(Splay\)内的实节点数,其实就相当于它的深度!
这样一来,询问操作就处理完了。
考虑修改生长节点操作
这应该是最烦的,也是比较难理解的一个操作了。
考虑我们修改生长节点之后,其实也就相当于在下一次修改生长操作之前,所有被修改生长节点的树被新加入的节点,父亲就从原先生长节点变成了新的生长节点。
而考虑没有被修改生长节点与被修改生长节点的两棵树的差异,其实也就是这些点的位置发生了变化。
如果我们要按照之前提到的离线思想,我们每次修改时,就相当于要将这一坨节点从一个节点的儿子被移作另一个节点的儿子。
而要快速移动的话,就需要新建一个虚点,然后将这些节点全部作为这个虚点的儿子,并将虚点作为目标节点的儿子,这样要移动儿子直接将这个虚点在\(LCT\)上\(Cut\)并\(Link\)一下即可。
而具体实现中还有一定细节,可参考代码。
代码
#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 M 200000
#define swap(x,y) (x^=y^=x^=y)
#define Gmin(x,y) (x>(y)&&(x=(y)))
#define Gmax(x,y) (x<(y)&&(x=(y)))
#define pb push_back
using namespace std;
int n,m,RtoA[M+5],IsReal[M+5],L[M+5],R[M+5],ans[M+5];
struct Op {int p,x,y;I Op(CI t=0,CI a=0,CI b=0):p(t),x(a),y(b){}};
vector<Op> v[N+5];
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^FS?FO[C++]=c:(fwrite(FO,1,C,stdout),FO[(C=0)++]=c))
		#define tn (x<<3)+(x<<1)
		#define D isdigit(c=tc())
		int T,C;char c,*A,*B,FI[FS],FO[FS],S[FS];
	public:
		I FastIO() {A=B=FI;}
		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--]);}
		Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
		Tp I void writeln(Con Ty& x) {write(x),pc('\n');}
		I void clear() {fwrite(FO,1,C,stdout);}
}F;
class LinkCutTree//不换根LCT
{
	private:
		#define PU(x) (O[x].Sz=O[O[x].S[0]].Sz+O[O[x].S[1]].Sz+IsReal[x])
		#define Re(x) (swap(O[x].S[0],O[x].S[1]),O[x].R^=1)
		#define PD(x) (O[x].R&&(Re(O[x].S[0]),Re(O[x].S[1]),O[x].R=0))
		#define Wh(x) (O[O[x].F].S[1]==x)
		#define Co(x,y,d) (O[O[x].F=y].S[d]=x)
		#define IR(x) (O[O[x].F].S[0]^x&&O[O[x].F].S[1]^x)
		int St[M+5];struct node {int Sz,R,F,S[2];}O[M+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),PU(f);
		}
		I void S(RI x)
		{
			RI f=x,T=0;W(St[++T]=f,!IR(f)) f=O[f].F;W(T) PD(St[T]),--T;
			W(!IR(x)) f=O[x].F,!IR(f)&&(Ro(Wh(f)^Wh(x)?x:f),0),Ro(x);PU(x);
		}
		I int Ac(RI x) {RI s;for(s=0;x;x=O[s=x].F) S(x),O[x].S[1]=s,PU(x);return s;}//此处Access返回最后操作的节点编号,用于求LCA
	public:
		I void Link(CI x,CI y) {S(x),O[x].F=y;}//连边
		I void Cut(CI x) {Ac(x),S(x),O[x].S[0]=O[O[x].S[0]].F=0,PU(x);}//删掉与父亲的边
		I int GetDis(CI x,CI y)//求距离
		{
			RI t,dx,dy;Ac(x),S(x),dx=O[x].Sz,t=Ac(y),S(y),dy=O[y].Sz;//求出Depth[x]和Depth[y],同时求出LCA(x,y)
			return Ac(t),S(t),dx+dy-(O[t].Sz<<1);//求出Depth[LCA],从而计算x与y的距离
		}
}LCT;
#define BuildR(x,y) (IsReal[RtoA[++CR]=++CA]=1,L[CR]=x,R[CR]=y)//新建一个实际存在于[l,r]树内的实点
int main()
{
	freopen("forest.in","r",stdin),freopen("forest.out","w",stdout);
	RI i,j,sz,op,x,y,z,lstV,CA=0,CR=0,Qcnt=0;
	for(F.read(n,m),BuildR(1,n),LCT.Link(lstV=++CA,CR),i=1;i<=m;++i)//初始化,建立实点1,并给1建一个虚点
	{
		switch(F.read(op,x,y),op)
		{
			case 0:BuildR(x,y),LCT.Link(CA,lstV);break;//增加节点,在LCT中与上一个虚点连边,方便转移
			case 1:
				if(F.read(z),Gmax(x,L[z]),Gmin(y,R[z]),x>y) continue;//如果要修改的区间为空,则跳过
				LCT.Link(++CA,lstV),v[x].pb(Op(-1,CA,RtoA[z])),v[y+1].pb(Op(-1,CA,lstV)),
				lstV=CA;break;//新建虚点,并存下此操作
			case 2:F.read(z),v[x].pb(Op(++Qcnt,RtoA[y],RtoA[z]));break;//存下此操作
		}
	}
	for(i=1;i<=n;++i) for(sz=v[i].size(),j=0;j^sz;++j)//从左到右扫描每一棵树
		~v[i][j].p?ans[v[i][j].p]=LCT.GetDis(v[i][j].x,v[i][j].y)://处理询问
		(LCT.Cut(v[i][j].x),LCT.Link(v[i][j].x,v[i][j].y),0);//更改生长节点
	for(i=1;i<=Qcnt;++i) F.writeln(ans[i]);return F.clear(),0;//输出答案
}
【BZOJ4573】[ZJOI2016] 大森林(LCT)的更多相关文章
- [ZJOI2016]大森林(LCT)
		题目描述 小Y家里有一个大森林,里面有n棵树,编号从1到n.一开始这些树都只是树苗,只有一个节点,标号为1.这些树都有一个特殊的节点,我们称之为生长节点,这些节点有生长出子节点的能力. 小Y掌握了一种 ... 
- 洛谷P3348 [ZJOI2016]大森林 [LCT]
		传送门 刷了那么久水题之后终于有一题可以来写写博客了. 但是这题太神仙了我还没完全弄懂-- upd:写完博客之后似乎懂了. 思路 首先很容易想到\(O(n^2\log n)\)乘上\(O(\frac{ ... 
- bzoj 4573: [Zjoi2016]大森林 lct splay
		http://www.lydsy.com/JudgeOnline/problem.php?id=4573 http://blog.csdn.net/lych_cys/article/details/5 ... 
- [BZOJ4573][ZJOI2016]大♂森林
		bzoj luogu uoj sol \(orz\ \ HJT\ \ dalao\)教会我做这道题. 考虑每两个相邻位置的树的差异. 对于一个1操作(更换生长节点),假设区间是\([l,r]\),那么 ... 
- BZOJ4573 : [Zjoi2016]大森林
		扫描线,从左到右依次处理每棵树. 用set按时间顺序维护影响了这棵树的所有操作,那么一个点的父亲就是它前面第一个操作1. 用Splay维护树的括号序列,那么两点间的距离就是括号数量减去匹配的括号个数. ... 
- BZOJ4573:[ZJOI2016]大森林——题解
		http://www.lydsy.com/JudgeOnline/problem.php?id=4573 https://www.luogu.org/problemnew/show/P3348#sub ... 
- [ZJOI2016]大森林
		Description: 小Y家里有一个大森林,里面有n棵树,编号从1到n 0 l r 表示将第 l 棵树到第 r 棵树的生长节点下面长出一个子节点,子节点的标号为上一个 0 号操作叶子标号加 1(例 ... 
- 【刷题】BZOJ 4573 [Zjoi2016]大森林
		Description 小Y家里有一个大森林,里面有n棵树,编号从1到n.一开始这些树都只是树苗,只有一个节点,标号为1.这些树都有一个特殊的节点,我们称之为生长节点,这些节点有生长出子节点的能力.小 ... 
- P3348 [ZJOI2016]大森林
		\(\color{#0066ff}{ 题目描述 }\) 小Y家里有一个大森林,里面有n棵树,编号从1到n.一开始这些树都只是树苗,只有一个节点,标号为1.这些树都有一个特殊的节点,我们称之为生长节点, ... 
- bzoj 4573: [Zjoi2016]大森林
		Description 小Y家里有一个大森林,里面有n棵树,编号从1到n.一开始这些树都只是树苗,只有一个节点,标号为1.这些树 都有一个特殊的节点,我们称之为生长节点,这些节点有生长出子节点的能力. ... 
随机推荐
- PIE SDK Geometry的坐标转换
			1. 基于SpatialReference对象的坐标转换 1.1 示例简介 Geometry类是所有几何形体对象的父类,它是一个抽象类,IGeometry接口定义了所有的几何对象都有的方法和属性. 下 ... 
- 【python爬虫】利用selenium和Chrome浏览器进行自动化网页搜索与浏览
			功能简介:利用利用selenium和Chrome浏览器,让其自动打开百度页面,并设置为每页显示50条,接着在百度的搜索框中输入selenium,进行查询.然后再打开的页面中选中“Selenium - ... 
- linux下统计文本行数的各种方法(二)
			上一篇讲的都是统计单个文件的方法,直接在命令行执行就可以.现在试试脚本的方式,统计多个文件的行数 一.统计目录下所有文件的文件数及所有行数 脚本暂时命名为count.sh,代码如下: #!/bin/b ... 
- 腾讯毛华:智能交互,AI助力下的新生态
			欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~ 演讲人:毛华 腾讯云语音云总经理 背景:5月23-24日,以"焕启"为主题的腾讯"云+未来"峰会在广 ... 
- URAL ——1249——————【想法题】
			Ancient Necropolis Time Limit:5000MS Memory Limit:4096KB 64bit IO Format:%I64d & %I64u ... 
- 重构指南 - 移除重复内容(Remove Duplication)
			在项目中或多或少的都存在着重复的或者功能相似的代码,如果要对代码做改动,就要修改多个地方,所以我们需要将多处重复的代码提取到一个公共的地方供统一调用,以减少代码量,提高代码可维护性. 重构前代码 pu ... 
- laravel vue.js的使用
			安装cors 地址:https://github.com/barryvdh/laravel-cors 在Kernel文件中加上 protected $middleware = [ \Barryv ... 
- Csharp:user WebControl Read Adobe PDF Files In Your Web Browser
			namespace GeovinDu.PdfViewer { [DefaultProperty("FilePath")] [ToolboxData("<{0}:Sh ... 
- vue.extend与vue.component的区别和联系
			一味的闷头开发,却对基础概念缺乏理解,是个大坑... 查阅官网后现对自己的理解记录一下,用于日后复习巩固 Vue.extend({}) 简述:使用vue.extend返回一个子类构造函数,也就是预设部 ... 
- Web前端面试指导(十):元素定位有哪些?
			本题点评 在web前端中,元素定位是必须掌握的,是网页制作的必备技能,也是衡量是否为一个合格的web前端开发的标准之一,在网页设计中,很多地方都需要使用定位,例如菜单弹出,提示信息层等都需要定位.所以 ... 
