【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地图范围设置
1.功能简介 地图范围设置主要就是对图层的地图浏览控制,例如地图的放大.缩小.漫游.全图显示.1:1视图.比例尺等功能,能更好的与地图有一个互动的地图浏览体验.PIE SDK对地图范围设置主要利用IC ...
- Knime读取Jason数据
Knime ETL 工具 Jason数据解析到DB 1. 下面例子是一段Jason代码 [{,,},{,,},{,,}] 2. 用文本文件存储上面代码. test_jason.txt 3. 用File ...
- (转)Shell中获取字符串长度的七种方法
Shell中获取字符串长度的七种方法 原文:http://blog.csdn.net/jerry_1126/article/details/51835119 求字符串操作在shell脚本中很常用,下面 ...
- Redis启动和关闭
带配置文件启动 ./redis-server redis.conf 关闭 无密码模式 ./redis-cli -h xxx -p xxx shutdown 密码模式 ./redis-cli -h ...
- pip使用的基本命令
基本的命令解释,如下图: 安装 sudo easy_install pip 列出已安装的包 pip freeze or pip list 导出requirements.txt pip freeze & ...
- Java入门系列-06-运算符
这篇文章为你搞懂2个问题 java 中的常用运算符有哪些?如何使用? 这些运算符的运算优先级是怎样的? 算数运算符 明显是做数学运算的,包括以下符号: + 加法运算 敲一敲: public class ...
- JS常用的设计模式(6)——桥接模式
桥接模式的作用在于将实现部分和抽象部分分离开来, 以便两者可以独立的变化.在实现api的时候, 桥接模式特别有用.比如最开始的singleton的例子. var singleton = functio ...
- js.css嵌入dll
处理请求,返回 public ActionResult Get() { //传递一个部分名称 var n = Request["n"]; n = n.Replace('/', '. ...
- 微软的深度学习框架cntk ,我目前见过 安装方式最简单的一个框架,2.0之后开始支持C# 咯
wiki:https://github.com/Microsoft/CNTK/wiki 嗨,你也是我这种手残党么?之前试着安装着mxnet和tensorflow,但是因为时间比较短所以往往来不及安装完 ...
- Implementation: Quick Sort 2014-08-19
#include <stdio.h> void print(int *a, int start , int end); void quick_sort(int *a, int start, ...