[ZJOI2016]大森林
XXI.[ZJOI2016]大森林
论LCT的百种用法系列
这题有几个性质:
1.询问与时间无关。因为只是添加新点,原来点之间的位置关系不变。因此只要询问的两个点都被建出来了,何时进行询问并无影响。
2.操作重叠的部分很多。因为我们不管怎么加点,即使是加原树中没有的点,仍然有原来点之间的位置关系不变。因此,我们每次加点操作都可以默认是所有树全都加点,不需要管原本的限制。这样的话,某些相似的部分可能在很多树上都出现了。我们是否可以把相似的部分提取出来呢?
我们考虑差分对于生长节点的修改。
对于每次修改,我们都新建一个虚点。在下一次修改之前,所有的生长效果都是在这个虚点上再挂上一个点。这样的话,就在更改生长节点时,就可以直接虚点一断一连即可。
生长操作就是这段代码:
if(t1==0)insnewreal(t2,t3),link(cntofall,lastvir);//a new real node, linked to the previous virtual
对于修改操作:
首先,关注到原题中的一段话:
表示将第 棵树到第 棵树的生长节点改到标号为 的节点。对于 这棵树,如果标号 的点不在其中,那么这个操作对该树不产生影响。
因为我们的生长都是所有树全都长,那就意味着有些原本没有点的树也长出了点。
因此,我们这个修改区间应该与号节点的生长区间取交集。如果交集为空,直接跳过此次修改。
修改操作步骤如下:
-
取并集
-
新建代表此次修改的虚点,并将其父亲设为上次修改的虚点(即一下)。
-
在树处压入操作“将这次修改的虚点连到这次修改的目标生长节点上”(这应用了扫描线的思想)。
-
在树处压入操作“将这次修改的虚点重新连回上次修改的虚点”。
-
将“上次修改的虚点”这一统计变量赋成本次修改的虚点。
对应代码:
if(t1==1){
scanf("%d",&t4);
t2=max(t2,nl[t4]);
t3=min(t3,nr[t4]);
if(t2>t3)continue;
link(++cntofall,lastvir);//add a new virtual node
v[t2].push_back(op(-1,cntofall,realid[t4]));//initially,the new virtual is linked to lastvir;after TREE t2, it is now linked to realid[t4];
v[t3+1].push_back(op(-1,cntofall,lastvir));//and after t3+1, the new virtual is linked to lastvir again.
lastvir=cntofall;
}
然后就是按照从到的顺序遍历每棵树,依次进行修改和询问了。
哦,对,询问,怎么办呢?
首先这题我们最好不要写无根LCT,即使用的LCT。因为操作有点多,这个无根LCT的常数极大,按照我这种不写快读的写法是妥妥T的。那么我们应该如何实现查询路径长度呢?
首先一个非常合情合理的想法就是树上差分。假定我们要查询的是点对,我们就找到,并尝试用一些值拼出路径长度。
假设我们已经找出,则我们只需要在每个节点维护splay中实子树大小,然后,分别求出到、到、到的路径长度(当然,只包括实点,虚点不算)。答案即为。
代码:
#include<bits/stdc++.h>
using namespace std;
#define lson t[x].ch[0]
#define rson t[x].ch[1]
int n,m,cntofreal,cntofall,cntofquery,lastvir,realid[200100],nl[200100],nr[200100],res[200100];
struct LCT{
int fa,ch[2],sz,real;
}t[1001000];
inline int identify(int x){
if(x==t[t[x].fa].ch[0])return 0;
if(x==t[t[x].fa].ch[1])return 1;
return -1;
}
inline void pushup(int x){t[x].sz=t[lson].sz+t[rson].sz+t[x].real;}
inline void rotate(int x){
register int y=t[x].fa,z=t[y].fa,dirx=identify(x),diry=identify(y),b=t[x].ch[!dirx];
if(diry!=-1)t[z].ch[diry]=x;t[x].fa=z;
if(b)t[b].fa=y;t[y].ch[dirx]=b;
t[y].fa=x,t[x].ch[!dirx]=y;
pushup(y),pushup(x);
}
inline void splay(int x){for(;identify(x)!=-1;rotate(x))if(identify(t[x].fa)!=-1)rotate(identify(x)==identify(t[x].fa)?t[x].fa:x);}
inline int access(int x){register int y=0;for(;x;x=t[y=x].fa)splay(x),rson=y,pushup(x);return y;}
inline int findroot(int x){access(x),splay(x);while(lson)x=lson;splay(x);return x;}
inline void link(int x,int y){access(x),splay(x),t[x].fa=y;}
inline void cut(int x){access(x),splay(x),lson=t[lson].fa=0,pushup(x);}
inline int getdis(int x,int y){
int sx,sy,sl,lca;
access(x),splay(x),sx=t[x].sz;
lca=access(y),splay(y),sy=t[y].sz;
access(lca),splay(lca),sl=t[lca].sz;
return sx+sy-2*sl;
}
void insnewreal(int l,int r){//insert a new node for all trees in section [l,r]
t[realid[++cntofreal]=++cntofall].real=true;//cnt of all,the ID of all nodes in the LCT; real id,the ID of all the real nodes in the LCT
nl[cntofreal]=l,nr[cntofreal]=r;//cnt of real, the ID of added real nodes
}
struct op{
int id,x,y;
op(int a=0,int b=0,int c=0){id=a,x=b,y=c;}
};
vector<op>v[100100];
int main(){
scanf("%d%d",&n,&m);
insnewreal(1,n);//initially, one real node for all
link(++cntofall,1);//a initial virtual node, linking to the initial real node
lastvir=cntofall;//last virtual, the previous virtual node's ID.
for(int i=1,t1,t2,t3,t4;i<=m;i++){
scanf("%d%d%d",&t1,&t2,&t3);
if(t1==0)insnewreal(t2,t3),link(cntofall,lastvir);//a new real node, linked to the previous virtual
if(t1==1){
scanf("%d",&t4);
t2=max(t2,nl[t4]);
t3=min(t3,nr[t4]);
if(t2>t3)continue;
link(++cntofall,lastvir);//add a new virtual node
v[t2].push_back(op(-1,cntofall,realid[t4]));//initially,the new virtual is linked to lastvir;after TREE t2, it is now linked to realid[t4];
v[t3+1].push_back(op(-1,cntofall,lastvir));//and after t3+1, the new virtual is linked to lastvir again.
lastvir=cntofall;
}
if(t1==2)scanf("%d",&t4),v[t2].push_back(op(++cntofquery,realid[t3],realid[t4]));
}
for(int i=1;i<=n;i++)for(int j=0;j<v[i].size();j++){
if(v[i][j].id!=-1)res[v[i][j].id]=getdis(v[i][j].x,v[i][j].y);
else cut(v[i][j].x),link(v[i][j].x,v[i][j].y);
}
for(int i=1;i<=cntofquery;i++)printf("%d\n",res[i]);
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?