题目链接

题目描述

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

小Y掌握了一种魔法,能让第l棵树到第r棵树的生长节点长出一个子节点。同时她还能修改第l棵树到第r棵树的生长节点。她告诉了你她使用魔法的记录,你能不能管理她家的森林,并且回答她的询问呢?

Sol

一脸毒瘤。

区间 \(link\) 一个叶子 , 显然不能模拟吧...

这种复杂的题显然都是只寻求我们需要知道的信息。

我们只需要知道某棵树里两个点之间的距离就行了。

注意到题目中说了保证 \(u,v\) 是存在于要询问的树中的 , 容易发现我们先把树该加的点都加了最后再来询问也是木有关系的。

这样再想一下,每次加叶子的编号是一定的 !! 所以那个什么区间加叶子也是假的 , 我们并不关心我们是否加错了叶子 , 反正不影响询问。\((OVO)\)

所以我们仅仅需要一棵树。

但是一棵树显然不能同时处理不同树上的询问。

这时就容易发现这是一个二维空间里的问题了 , 以时间和位置为轴把操作写在二维平面上的话 , 就可以用一个扫描线来维护每一个位置上树的形态了并求解每一棵树的询问了。

唯一要考虑的就是换根了。

我们需要换根的话还不就是叶子接错地方了嘛 , 考虑什么情况下我们叶子接错了地方。

首先需要有一个换根操作 , 之后我们接了点叶子在原来的根上 , 就 wa 了。

所以我们要把它们搬运回去。也就是说 , 对于一个区间 \([L,R]\) 的换根操作。

当扫描线到了 \(L\) 的时候 , 我们需要把时间在此换根操作之后的点从原来的父亲上扯下来连到当前的新的根上 , 也就是后缀换根\(\dots\)然后出了这个区间后,也就是在 \(R+1\) 的位置 , 我们要把他们又全部插回去。

怎样高效地做到这种奇葩的操作。

我们对于每一个换根操作建立一个虚点 , 把生长操作以及换根操作的(虚)点都直接连到他们时间上的前驱虚点上。(特别的,没有前驱虚点的连在1号点上)

一开始 , 由于所有点都连向了虚点 , 那么如果删去虚点 , 这样就是一个菊花图。

之后 , 按照扫描线的顺序进行操作 , 改换根的就直接把这个虚点换个父亲根 , 要插回去就一样的移动虚点 , 这样不就后缀换根了吗!

要注意的细节地方就是求距离了 , 由于建立了虚点 , 不能直接算距离 , 但是还是可以用\(dis(u,v)=dis(u,root)+dis(v,root)-2*dis(LCA(u,v),root)\)来正确计算距离 , 每次计算前让1号点成为根就行了。

code:

#include<bits/stdc++.h>
using namespace std;
template<class T>inline void init(T&x){
x=0;char ch=getchar();bool t=0;
for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') t=1;
for(;ch>='0'&&ch<='9';ch=getchar()) x=(x<<1)+(x<<3)+(ch-48);
if(t) x=-x;return;
}
#define ls son[0]
#define rs son[1]
#define __ NULL
#define get_son(a) (a->fa->rs==a)
#define get(a,b,c) (a? a->b:c)
#define IS(a) (a&&(!a->fa||(a->fa->son[get_son(a)]!=a)))
#define ID(a) ((a)-T)
int n,m;
const int N=4e5+10;
struct node{
node*son[2],*fa;bool rev;int size,cnt;
node* Tfa;
node(){ls=rs=fa=Tfa=__,size=rev=0;}
}T[N],*stk[N];int top=0,cnt=0;
node *aim[N];
inline void push_down(node*p){
if(!p||!p->rev) return;swap(p->ls,p->rs),p->rev=0;
if(p->ls) p->ls->rev^=1;if(p->rs) p->rs->rev^=1;return;
}
inline void Push(node*p){
stk[top=1]=p;while(!IS(p)) p=p->fa,stk[++top]=p;
while(top) push_down(stk[top--]);return;
}
inline void update(node*p){if(p)p->size=get(p->ls,size,0)+get(p->rs,size,0)+p->cnt;}
inline void rotate(node*p){
int k=get_son(p);node*q=p->fa,*gp=p->fa->fa;
q->son[k]=p->son[k^1];
if(p->son[k^1]) p->son[k^1]->fa=q;
if(!IS(q)) gp->son[get_son(q)]=p;
p->fa=gp,q->fa=p,p->son[k^1]=q;
return update(q);
}
inline void Splay(node*p){
if(!p)return;Push(p);
for(;!IS(p);rotate(p)) if(IS(p->fa))continue;else get_son(p->fa)==get_son(p)? rotate(p->fa):rotate(p);
return update(p);
}
inline node* access(node*p){node*lst=p;for(node*pre=__;p;pre=p,p=p->fa)Splay(p),lst=p,p->rs=pre,update(p);return lst;}
inline node* find(node*p){for(access(p),Splay(p);p->ls;p=p->ls);return p;}
inline void make_root(node*p){access(p),Splay(p),p->rev^=1;}
inline void split(node*p,node*q){make_root(p),access(q),Splay(q);}
inline void link(node*p,node*q){split(p,q);p->fa=q;}
inline void cut(node*p,node*q) {split(p,q);if(q->ls==p)p->fa=q->ls=__,update(q);}
struct Qry{
int op,pos,t,A,B;
inline bool operator <(Qry B)const{if(pos!=B.pos) return pos<B.pos;return t<B.t;}
}Q[N];
struct Que{
int id,pos,u,v;
inline bool operator <(Que B)const{return pos<B.pos;}
}que[N];int Qid=0;
int num0=0,num1=0,tot=0;
int ans[N],L[N],R[N];
inline int Query(node*p,node*q){
make_root(&T[1]);
access(p);Splay(p);
int dep1=get(p->ls,size,0)+p->cnt-1;
node*lca=access(q);Splay(q);
int dep2=get(q->ls,size,0)+q->cnt-1;
access(lca);Splay(lca);
int dep3=lca? (get(lca->ls,size,0)+lca->cnt-1):0;
int res=dep1+dep2-(dep3<<1);
return res;
}
int main()
{
init(n),init(m);
cnt=1;L[1]=1,R[1]=n;
T[1].size=T[1].cnt=1;T[1].Tfa=__;
for(int i=1;i<=m;++i) {
int op;init(op);
int x,u,v,l,r;
if(op==2) {
init(x);init(u),init(v);
++Qid;que[Qid]=(Que){Qid,x,u,v};
}
else if(op==0) {
++num0;init(l),init(r);
L[num0+1]=l;R[num0+1]=r;
Q[++tot]=(Qry){op,l,i,num1,r};
}
else {
init(l),init(r),init(x);
l=max(l,L[x]),r=min(r,R[x]);
if(l<=r) {
++tot;++num1;
Q[tot]=(Qry){1,l,i,num1,x};
++tot;
Q[tot]=(Qry){2,r+1,i,num1,num1-1};
}
}
}cnt=num0+1;aim[0]=&T[1];int h=0,preid=1;
for(int i=1;i<=cnt;++i) T[i].size=T[i].cnt=1;
for(int i=1;i<=tot;++i) {
if(Q[i].op==0){++preid;T[preid].fa=T[preid].Tfa=aim[h];}
else {if(Q[i].t==Q[i-1].t) continue;++h;aim[h]=&T[++cnt];T[cnt].fa=T[cnt].Tfa=aim[h-1];}
}
sort(que+1,que+1+Qid);sort(Q+1,Q+1+tot);h=1;
for(int i=1;i<=tot;++i) {
while(h<=Qid&&Q[i].pos>que[h].pos){ans[que[h].id]=Query(&T[que[h].u],&T[que[h].v]);++h;}
if(Q[i].op==0) continue;
else {
int u=Q[i].A,v=Q[i].B;
if(Q[i].op==1) {
cut(aim[u],aim[u]->Tfa);
aim[u]->Tfa=&T[v];
link(aim[u],&T[v]);
}else {
cut(aim[u],aim[u]->Tfa);
aim[u]->Tfa=aim[v];
link(aim[u],aim[v]);
}
}
}
while(h<=Qid) ans[que[h].id]=Query(&T[que[h].u],&T[que[h].v]),++h;
for(int i=1;i<=Qid;++i) printf("%d\n",ans[i]);
}

【LuoguP3348】[ZJOI2016]大森林的更多相关文章

  1. [ZJOI2016]大森林(LCT)

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

  2. [ZJOI2016]大森林

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

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

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

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

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

  5. bzoj 4573: [Zjoi2016]大森林

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

  6. P3348 [ZJOI2016]大森林

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

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

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

  8. BZOJ4573 : [Zjoi2016]大森林

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

  9. ●洛谷P3348 [ZJOI2016]大森林

    题链: https://www.luogu.org/problemnew/show/P3348 题解: LCT,神题 首先有这么一个结论: 每次的1操作(改变生长点操作),一定只会会对连续的一段区间产 ...

随机推荐

  1. gson转换json到bean时重命名

    @Expose   @SerializedName("0001") public Map<String,ChannelBean> c0001 = new HashMap ...

  2. Django信号量

    摘自官方文档 使用 信号 Django发送的所有信号的列表.使用该send()方法发送所有内置信号. 参见 有关如何注册和接收信号的信息,请参阅信号调度器上的文档. 用户登录/注销时,身份验证框架会 ...

  3. C# 文件打开对话框 图片fitter

    "All Image Files|*.bmp;*.ico;*.gif;*.jpeg;*.jpg;*.png;*.tif;*.tiff|""Windows Bitmap(* ...

  4. numpy的linalg.norm()函数求范数

    函数签名:def norm(x, ord=None, axis=None, keepdims=False) 其中ord参数表示求什么类型的范数,具体参见下表 下面是用代码对一个列表求上面的范数 imp ...

  5. 【Linux开发】计算机底层是如何访问显卡的?

    1. 显卡驱动是怎么控制显卡的, 就是说, 使用那些指令控制显卡, 通过端口么? 2. DirectX 或 OpenGL 或 CUDA 或 OpenCL 怎么找到显卡驱动, 显卡驱动是不是要为他们提供 ...

  6. 使用使用dockerfile构建webapi镜像然后使用link和bridge两种方式进行桥接

    首先新增一个webapi的项目 项目核心代码 UserContext using Microsoft.EntityFrameworkCore; using System; using System.C ...

  7. 简述Vue的实例属性、实例方法

    1.实例属性 组件树访问 $parent -----> 用来访问当前组件实例的父实例: $root -----> 用来访问当前组件树的根实例,如果当前组件没有父实例,则$root表示当前组 ...

  8. Canvas入门01-基础知识

    定义一个canvas,直接在Html中使用canvas便签即可. <!DOCTYPE html> <html lang="en"> <head> ...

  9. Java第一周总结

    通过两周的Java学习最深刻的体会就是Java好像要比C要简单一些. 然后这两周我学习到了很多东西,李老师第一次上课就给我们介绍了Java的发展历程,同时还有Java工具jdk的发展历程. Java语 ...

  10. centos7使用kubeadm搭建kubernetes集群

    一.本地实验环境准备 服务器虚拟机准备 IP CPU 内存 hostname 192.168.222.129 >=2c >=2G master 192.168.222.130 >=2 ...