点此看题面

大致题意: 有\(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. 5.centos7 jenkins安装

    1.安装jdk 安装过程请参照,zookeeper 安装中的jdk安装章节 文章地址: 2.安装jenkins 添加Jenkins库到yum库,Jenkins将从这里下载安装. wget -O /et ...

  2. 采用MQTT协议实现android消息推送(4)选fusesource-mqtt-client为客户端

    1.简介 一个java写的mqtt客户端.项目地址: https://github.com/fusesource/mqtt-client 2.引入fusesource-mqtt-client库 Fil ...

  3. (转载) win10生成SSH keys

    (转载) win10生成 SSH keys:   SSH key 可以让你在你的电脑和Code服务器之间建立安全的加密连接.  先执行以下语句来判断是否已经存在本地公钥: cat ~/.ssh/id_ ...

  4. Java 单向链表学习

    Java 单向链表学习 链表等同于动态的数组:可以不同设定固定的空间,根据需要的内容动态的改变链表的占用空间和动态的数组同一形式:链表的使用可以更加便于操作. 链表的基本结构包括:链表工具类和节点类, ...

  5. oracle 笔记---(四)__数据字典

    数据字典 user_*  该视图存储了关于当前用户所拥有的对象的信息.(即所有在该用户模式下的对象) all_* 该试图存储了当前用户能够访问的对象的信息.(与user_*相比,all_* 并不需要拥 ...

  6. TOJ 1690 Cow Sorting (置换群)

    Description Farmer John's N (1 ≤ N ≤ 10,000) cows are lined up to be milked in the evening. Each cow ...

  7. FZU 2122——又见LKity——————【字符串匹配、暴力】

    Problem 2122 又见LKity Accept: 407    Submit: 1413Time Limit: 1000 mSec    Memory Limit : 32768 KB  Pr ...

  8. Django 实现组合条件搜索、jsonp跨域请求

    1.类似于汽车之家的条件组合搜索那样 代码:http://pan.baidu.com/s/1nu7vZYD 2.jsonp实现跨域请求(在自己网页自动调用其他网站的接口,并将获取的数据呈现在自己网页上 ...

  9. python 批量ping服务器

    最近在https://pypi.python.org/pypi/mping/0.1.2找到了一个python包,可以用它来批量ping服务器,它是中国的大神写的,支持单个服务器.将服务器IP写在txt ...

  10. C#异步编程模型

    什么是异步编程模型 异步编程模型(Asynchronous Programming Model,简称APM)是C#1.1支持的一种实现异步操作的编程模型,虽然已经比较“古老”了,但是依然可以学习一下的 ...