【LuoguP3348】[ZJOI2016]大森林

题目链接

题目描述

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

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

Sol

一脸毒瘤。
区间 l i n k link link 一个叶子 , 显然不能模拟吧…
这种复杂的题显然都是只寻求我们需要知道的信息。
我们只需要知道某棵树里两个点之间的距离就行了。
注意到题目中说了保证 u , v u,v u,v 是存在于要询问的树中的 , 容易发现我们先把树该加的点都加了最后再来询问也是木有关系的。
这样再想一下,每次加叶子的编号是一定的 !! 所以那个什么区间加叶子也是假的 , 我们并不关心我们是否加错了叶子 , 反正不影响询问。 ( O V O ) (OVO) (OVO)
所以我们仅仅需要一棵树。
但是一棵树显然不能同时处理不同树上的询问。
这时就容易发现这是一个二维空间里的问题了 , 以时间和位置为轴把操作写在二维平面上的话 , 就可以用一个扫描线来维护每一个位置上树的形态了并求解每一棵树的询问了。

唯一要考虑的就是换根了。
我们需要换根的话还不就是叶子接错地方了嘛 , 考虑什么情况下我们叶子接错了地方。
首先需要有一个换根操作 , 之后我们接了点叶子在原来的根上 , 就 wa 了。
所以我们要把它们搬运回去。也就是说 , 对于一个区间 [ L , R ] [L,R] [L,R] 的换根操作。
当扫描线到了 L L L 的时候 , 我们需要把时间在此换根操作之后的点从原来的父亲上扯下来连到当前的新的根上 , 也就是后缀换根 … \dots 然后出了这个区间后,也就是在 R + 1 R+1 R+1 的位置 , 我们要把他们又全部插回去。
怎样高效地做到这种奇葩的操作。

我们对于每一个换根操作建立一个虚点 , 把生长操作以及换根操作的(虚)点都直接连到他们时间上的前驱虚点上。(特别的,没有前驱虚点的连在1号点上)
一开始 , 由于所有点都连向了虚点 , 那么如果删去虚点 , 这样就是一个菊花图。
之后 , 按照扫描线的顺序进行操作 , 改换根的就直接把这个虚点换个父亲根 , 要插回去就一样的移动虚点 , 这样不就后缀换根了吗!
要注意的细节地方就是求距离了 , 由于建立了虚点 , 不能直接算距离 , 但是还是可以用 d i s ( u , v ) = d i s ( u , r o o t ) + d i s ( v , r o o t ) − 2 ∗ d i s ( L C A ( u , v ) , r o o t ) dis(u,v)=dis(u,root)+dis(v,root)-2*dis(LCA(u,v),root) dis(u,v)=dis(u,root)+dis(v,root)2dis(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=
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值