[ZJOI2016]大森林

XXI.[ZJOI2016]大森林

论LCT的百种用法系列

这题有几个性质:

1.询问与时间无关。因为只是添加新点,原来点之间的位置关系不变。因此只要询问的两个点都被建出来了,何时进行询问并无影响。

2.操作重叠的部分很多。因为我们不管怎么加点,即使是加原树中没有的点,仍然有原来点之间的位置关系不变。因此,我们每次加点操作都可以默认是所有树全都加点,不需要管原本l,r的限制。这样的话,某些相似的部分可能在很多树上都出现了。我们是否可以把相似的部分提取出来呢?

我们考虑差分对于生长节点的修改

对于每次修改,我们都新建一个虚点。在下一次修改之前,所有的生长效果都是在这个虚点上再挂上一个点。这样的话,就在更改生长节点时,就可以直接虚点一断一连即可。

生长操作就是这段代码:

if(t1==0)insnewreal(t2,t3),link(cntofall,lastvir);//a new real node, linked to the previous virtual

对于修改操作:

首先,关注到原题中的一段话:

1 l r x 表示将第 l 棵树到第 r 棵树的生长节点改到标号为 x 的节点。对于 i (lir) 这棵树,如果标号 x 的点不在其中,那么这个操作对该树不产生影响

因为我们的生长都是所有树全都长,那就意味着有些原本没有x点的树也长出了x点。

因此,我们这个修改区间应该与x号节点的生长区间取交集。如果交集为空,直接跳过此次修改。

修改操作步骤如下:

  1. 取并集

  2. 新建代表此次修改的虚点,并将其父亲设为上次修改的虚点(即link一下)。

  3. 在树l处压入操作“将这次修改的虚点连到这次修改的目标生长节点上”(这应用了扫描线的思想)。

  4. 在树r+1处压入操作“将这次修改的虚点重新连回上次修改的虚点”。

  5. 将“上次修改的虚点”这一统计变量赋成本次修改的虚点。

对应代码:

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;
}

然后就是按照从1n的顺序遍历每棵树,依次进行修改和询问了。

哦,对,询问,怎么办呢?

首先这题我们最好不要写无根LCT,即使用makeroot的LCT。因为操作有点多,这个无根LCT的makeroot常数极大,按照我这种不写快读的写法是妥妥T的。那么我们应该如何实现查询路径长度呢?

首先一个非常合情合理的想法就是树上差分。假定我们要查询的是点对(x,y),我们就找到LCAx,y,并尝试用一些值拼出路径长度。

假设我们已经找出LCAx,y,则我们只需要在每个节点维护splay中实子树大小,然后access(x),access(y),access(LCA),分别求出xROOTyROOTLCAROOT的路径长度(当然,只包括实点,虚点不算size)。答案即为sx+sy2sLCA

代码:

#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;
}

posted @   Troverld  阅读(58)  评论(0编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?
点击右上角即可分享
微信分享提示