点此看题面

大致题意: 有\(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)的更多相关文章

  1. [ZJOI2016]大森林(LCT)

    题目描述 小Y家里有一个大森林,里面有n棵树,编号从1到n.一开始这些树都只是树苗,只有一个节点,标号为1.这些树都有一个特殊的节点,我们称之为生长节点,这些节点有生长出子节点的能力. 小Y掌握了一种 ...

  2. 洛谷P3348 [ZJOI2016]大森林 [LCT]

    传送门 刷了那么久水题之后终于有一题可以来写写博客了. 但是这题太神仙了我还没完全弄懂-- upd:写完博客之后似乎懂了. 思路 首先很容易想到\(O(n^2\log n)\)乘上\(O(\frac{ ...

  3. bzoj 4573: [Zjoi2016]大森林 lct splay

    http://www.lydsy.com/JudgeOnline/problem.php?id=4573 http://blog.csdn.net/lych_cys/article/details/5 ...

  4. [BZOJ4573][ZJOI2016]大♂森林

    bzoj luogu uoj sol \(orz\ \ HJT\ \ dalao\)教会我做这道题. 考虑每两个相邻位置的树的差异. 对于一个1操作(更换生长节点),假设区间是\([l,r]\),那么 ...

  5. BZOJ4573 : [Zjoi2016]大森林

    扫描线,从左到右依次处理每棵树. 用set按时间顺序维护影响了这棵树的所有操作,那么一个点的父亲就是它前面第一个操作1. 用Splay维护树的括号序列,那么两点间的距离就是括号数量减去匹配的括号个数. ...

  6. BZOJ4573:[ZJOI2016]大森林——题解

    http://www.lydsy.com/JudgeOnline/problem.php?id=4573 https://www.luogu.org/problemnew/show/P3348#sub ...

  7. [ZJOI2016]大森林

    Description: 小Y家里有一个大森林,里面有n棵树,编号从1到n 0 l r 表示将第 l 棵树到第 r 棵树的生长节点下面长出一个子节点,子节点的标号为上一个 0 号操作叶子标号加 1(例 ...

  8. 【刷题】BZOJ 4573 [Zjoi2016]大森林

    Description 小Y家里有一个大森林,里面有n棵树,编号从1到n.一开始这些树都只是树苗,只有一个节点,标号为1.这些树都有一个特殊的节点,我们称之为生长节点,这些节点有生长出子节点的能力.小 ...

  9. P3348 [ZJOI2016]大森林

    \(\color{#0066ff}{ 题目描述 }\) 小Y家里有一个大森林,里面有n棵树,编号从1到n.一开始这些树都只是树苗,只有一个节点,标号为1.这些树都有一个特殊的节点,我们称之为生长节点, ...

  10. bzoj 4573: [Zjoi2016]大森林

    Description 小Y家里有一个大森林,里面有n棵树,编号从1到n.一开始这些树都只是树苗,只有一个节点,标号为1.这些树 都有一个特殊的节点,我们称之为生长节点,这些节点有生长出子节点的能力. ...

随机推荐

  1. PIE SDK小波变换

    1.算法功能简介 小波变换是一种信号的时间——尺度分析方法,具有多分辨率分析的特点,而且在时频两域都具有表征信号局部特征的能力,是一种窗口大小固定不变但其形状可变,时间窗和频率窗都可变的时频局部化分析 ...

  2. PIE SDK地图书签

    地图书签,可以理解为暂时记录当前地图的范围和放大级别,在后续的操作中如果想回到地图之前的状态,就可以点击保存的书签就可以回到此状态,如图所示: 地图刚加载的时候是一幅世界地图 我们将地图的中心拖到南美 ...

  3. CAPL编程实现诊断刷写,车联网FOTA流程自动化测试(方案篇)

    原创内容,转载请注明出处 本文围绕车联网的ECU,TBOX的FOTA升级业务展开描述.主要讲如何通过CANoe编程实现自动化测试, 验证TBOX在FOTA业务过程中作为一个诊断仪刷写整车其它ECU的流 ...

  4. VUE--mixins的一些理解。

    概念:混入 (mixins) 是一种分发 Vue 组件中可复用功能的非常灵活的方式.混入对象可以包含任意组件选项.当组件使用混入对象时,所有混入对象的选项将被混入该组件本身的选项. 用法: 1.创建混 ...

  5. oracle 基础知识(九)----SQL解析

    一,解析过程 二,硬解析,软解析,软软解析 01,硬解析 将SQL语句通过监听器发送到Oracle时, 会触发一个Server process生成,来对该客户进程服务.Server process得到 ...

  6. 数据库~大叔通过脚本生成poco实体

    今天在做开发时,需要把表映射成实体,又没有EF这种工具,就从网上下了一个工具,但使用时觉得太重了,所以就自己写了一个,基于mysql的. 功能:输入表名,得到这个表的poco实体 SELECT COL ...

  7. Ansible 介绍和使用

    简介 Ansible是一个简单的自动化运维管理工具,基于Python语言实现,由Paramiko和PyYAML两个关键模块构建,可用于自动化部署应用.配置.编排task(持续交付.无宕机更新等).主版 ...

  8. 【卷土重来之C#学习笔记】(二)c#编程概述

    (1)开始C#,一个简单的程序Hello Word 开始     using System; //使用了System的命名空间 using System.Collections.Generic; us ...

  9. 【linux相识相知】网络属性配置

    当我们拥有一个崭新的计算机的时候,第一步恐怕都是迫不及待的下载各种软件,看视频,听音乐等,这里的关键的一点是要有网络.现在的个人计算机大部分都是windows操作系统的,接入网络网络很简单,插上网线也 ...

  10. 三种ajax解析模式!

    一.Ajax中的JSON格式 html代码: <html> <body> <input type="button" value="Ajax& ...