acm-LuoGu P3348 [ZJOI2016]大森林(LCT神仙题)

题面
传送门
本题的做法非常神奇,原理的思想需要仔细体会才能理解,这里只陈述客观的做法,原因不解释(太难解释了。

首先发现 2 2 2号操作可以放到最后处理,其次 0 0 0号操作可以认为是对所有的树进行一次统一的操作,因为 2 2 2号操作不会询问不存在树上的点,而 1 1 1号操作某个节点的时候需要与创建该节点的 0 0 0号操作的范围取一下交集。

考虑将操作离线处理,对于所有的 1 1 1号类型的操作都建立一个虚点,假设编号是 v 1 , v 2 , v 3 , . . . , v k v_1,v_2,v_3,...,v_k v1,v2,v3,...,vk,其中 k k k代表 1 1 1号类型操作的数目,然后建立 1 ← v 1 ← v 2 ← v 3 . . . ← v k 1\leftarrow v_1 \leftarrow v_2 \leftarrow v_3...\leftarrow v_k 1v1v2v3...vk的一条链,箭头指向的是父亲节点。
现在将所有的实点都连向向前离生成它的时间最近的操作 1 1 1对应的虚点,这样能够形成一个以 1 1 1为根节点的树,其中虚点的作用在于确定连向它的所有实点的父亲节点。

现在我们考虑相邻两颗树最终的差异,两棵树的差异其实是某些实节点的父亲的不同,这些实节点都连向了某个虚节点,因此改变虚节点父亲就可以改变对应的实节点父亲。

现在考虑差分,从 1 1 1号树开始,我们向编号大的树扫描,考虑它们的差异变化。所有 1 1 1号类型操作 l    r    x l\;r\;x lrx可以分为两个步骤,第一步是将树 l − 1 l-1 l1的对应到 l    r    x l\;r\;x lrx的虚拟节点父亲置为节点 x x x,第二步是将树 r + 1 r+1 r+1的对应到 l    r    x l\;r\;x lrx的虚拟节点父亲还原。

这些操作其实就是断边和连边操作,可以用 L C T LCT LCT实现。

最后是求距离,考虑求出两点间的 l c a lca lca,对于 u , v u,v u,v而言,我们先 a c c e s s ( u ) access(u) access(u),再 a c c e s s ( v ) access(v) access(v),最后执行 c h [ x ] [ 1 ] = y ch[x][1]=y ch[x][1]=y x x x就是对应的 l c a lca lca,那么距离也就好求了。

#include <bits/stdc++.h>
using namespace std;

const int maxn = 4e5+1e4;

int ch[maxn][2],val[maxn],fa[maxn],sz[maxn],par[maxn],rangel[maxn],ranger[maxn],tot=1,ans[maxn];
struct Node{
	int op,id,x;
};
struct OP{
	int op,a,b,c;
};
struct QRY{
	int id,u,v;
};
vector<Node>g[maxn];
vector<QRY>qry[maxn];

void pushup(int u){
	sz[u]=sz[ch[u][0]]+sz[ch[u][1]]+val[u];
}
bool is_root(int u){
	return ch[fa[u]][0]!=u && ch[fa[u]][1]!=u;
}
int dir(int u){
	return ch[fa[u]][1]==u;
} 
void connect(int u,int f,int d){
	if(u)fa[u]=f;
	if(f)ch[f][d]=u;
}
void rotate(int u){
	if(is_root(u))return;
	int f=fa[u],ff=fa[f],du=dir(u),df=dir(f);
	if(is_root(f))fa[u]=ff;
	else connect(u,ff,df);
	connect(ch[u][du^1],f,du),connect(f,u,du^1);
	pushup(f);
}

void splay(int u){
	for(int f=fa[u];!is_root(u);f=fa[u]){
		if(!is_root(f))(dir(u)^dir(f))?rotate(u):rotate(f);
		rotate(u); 
	}
	pushup(u);
}

int access(int u){
	int x,y;
	for(x=u,y=0;x;x=fa[y=x]){
		splay(x),ch[x][1]=y,pushup(x);
	}
	return y;
}

void link(int f,int u){
	par[u]=f;
	access(f);
	splay(f);
	access(u);
	splay(u);
	fa[u]=f;
}
void cut(int u){
	par[u]=0;
	access(u);
	splay(u);
	ch[u][0]=fa[ch[u][0]]=0;
	pushup(u);
}
int query(int u,int v){
	access(u);
	splay(u);
	int disu=sz[u]-1;
	int lca=access(v);
	splay(lca);
	int dislca=sz[ch[lca][0]]+val[lca]-1;
	splay(v);
	int disv=sz[v]-1;
	return disu+disv-2*dislca;
}
int main(){
	int n,m;
	scanf("%d%d",&n,&m);
	rangel[1]=1,ranger[1]=n;
	vector<OP>h;
	int cnt=1,ct=0;
	while(m--){
		int op,l,r,u,v,x;
		scanf("%d",&op);
		if(!op){
			scanf("%d%d",&l,&r);
			h.push_back({op,l,r});
			++cnt;
			rangel[cnt]=l,ranger[cnt]=r;
		}else if(op==1){
			scanf("%d%d%d",&l,&r,&x);
			h.push_back({op,l,r,x});
			ct++;
		}else{
			scanf("%d%d%d",&x,&u,&v);
			h.push_back({op,x,u,v});
		}
	}
	for(int i=1;i<=cnt;++i)val[i]=1,pushup(i);
	for(int i=1;i<=ct;++i){
		if(i==1)link(i,cnt+i);
		else link(cnt+i-1,cnt+i);
	}
	int now=1,ctt=0;ct=0;
	for(auto u:h){
		if(!u.op){
			link(now,++tot);
		}else if(u.op==1){
			++ct;now=cnt+ct;
			u.a=max(u.a,rangel[u.c]);
			u.b=min(u.b,ranger[u.c]);
			if(u.a>u.b)continue;
			g[u.a].push_back({0,cnt+ct,u.c});
			if(u.b+1<=n)g[u.b+1].push_back({1,cnt+ct,0});
		}else{
			qry[u.a].push_back({++ctt,u.b,u.c});
		}
	}
	for(int i=1;i<=n;++i){
		for(auto u:g[i]){
			if(!u.op){
				cut(u.id);
				link(u.x,u.id);
			}else{
				cut(u.id);
				if(u.id>cnt+1)link(u.id-1,u.id);
				else link(1,u.id); 
			}
		}
		for(auto u:qry[i]){
			ans[u.id]=query(u.u,u.v);
		}
	}
	for(int i=1;i<=ctt;++i)printf("%d\n",ans[i]);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值