动态笛卡尔树

这里讲的动态笛卡尔树的问题指的是你要对一个序列单点加值并维护笛卡尔树的形态信息。

由于具有严格偏序关系的数列对应的笛卡尔树唯一,我们只需要像维护 Treap 一样单点上旋即可。

原题题解里丢了一句 LCT 维护即可,然而在仔细研究过后,我发现这 std 里的做法根本不是经典的动态树的 LCT。事实上,这种做法维护的并不是像 LCT 一样任意的“实虚链剖分”,而是所谓的“直链剖分”(名字乱取的)。

这里所谓的 LCT 只是借用了 LCT 的 Aux-Tree 结构完成维护直链信息的操作。

具体地,如果在原树中一个点如果是他父亲的左儿子,那么它到左儿子的链是实链。右儿子对称。

这样的话,容易发现如果当前需要上旋的点到上旋目标位置是一条“直链“的话就可以直接在原树上 cut 两条边,link 两条边完成上旋。

然而真正难以处理的是上旋路径会有转折,如果暴力做上述过程的话是 \(O(\text{转折点数量})\)。然而,这样子暴力做均摊复杂度就是对的!以下设势能为虚链的总条数。

我们考虑对于一次上旋整体的复杂度进行分析,每一次对于直链的上旋“过继”儿子节点会增加一条实链,移动 \(x\) 节点会移动一条实链,同时 \(x\) 可能会有一个实儿子变虚。

看起来对势能可能会每一影响,但是注意到 \(x\) 的实儿子变虚后后面还会被“过继”成实链,最后只有 \(O(1)\) 的实儿子变虚,而会有 \(O(\text{转折点数量})\) 条虚链变实。

这样,每次可以使用虚链的势能支付直链的修改,每次用数据结构维护实虚链情况,每次操作实际代价 \(O(\log)\),总势能 \(O(n)\),复杂度是 \(O(n\log n)\)

注意到上述势能分析与 LCT 的有关“重虚链”的分析是有区别的,并不依赖于你维护 Aux-Tree 用的什么数据结构,所以理论上来说这种 LCT 内层用 fhq-treap 复杂度也是对的。

果然 LCT 扩展性还是挺大的。

代码实现比较有细节,需要一点技巧,下面这份代码实际上是我的考场假做法代码+LCT 模板题代码+标程拼起来的。

寄存一下代码(代码维护了笛卡尔树的四联通块个数):

#include <cstdio>
using namespace std;
typedef long long ll;
int read(){
	char c=getchar();int x=0;
	while(c<48||c>57) c=getchar();
	do x=(x<<1)+(x<<3)+(c^48),c=getchar();
	while(c>=48&&c<=57);
	return x;
}
const int N=500003;
typedef long long ll;
ll cur,a[N];
int cl[N],cr[N],ft[N];
int deg[N];
int n,q,rt;
int cal(int x,int y){
	if(!x||!y) return 0;
	return (deg[x]-1)*(deg[y]-1);
}
void trcut(int x,int y){
	if(!x||!y) return;
	cur-=cal(x,cl[x]);
	cur-=cal(x,cr[x]);
	cur-=cal(y,ft[y]);
	cur-=cal(y,cl[y]);
	cur-=cal(y,cr[y]);
	cur-=(deg[x]==3);
	cur-=(deg[y]==3);
	if(cl[y]==x) cl[y]=0;
	if(cr[y]==x) cr[y]=0;
	ft[x]=0;--deg[x];--deg[y];
	cur+=cal(x,cl[x]);
	cur+=cal(x,cr[x]);
	cur+=cal(y,ft[y]);
	cur+=cal(y,cl[y]);
	cur+=cal(y,cr[y]);
	cur+=(deg[x]==3);
	cur+=(deg[y]==3);
}
void trlink(int x,int y){
	if(!x||!y) return;
	cur-=cal(x,cl[x]);
	cur-=cal(x,cr[x]);
	cur-=cal(y,ft[y]);
	cur-=cal(y,cl[y]);
	cur-=cal(y,cr[y]);
	cur-=(deg[x]==3);
	cur-=(deg[y]==3);
	if(x<y) cl[y]=x;
	else cr[y]=x;
	ft[x]=y;++deg[x];++deg[y];
	cur+=cal(x,cl[x]);
	cur+=cal(x,cr[x]);
	cur+=cal(y,ft[y]);
	cur+=cal(y,cl[y]);
	cur+=cal(y,cr[y]);
	cur+=(deg[x]==3);
	cur+=(deg[y]==3);
}
int fa[N],tr[N][2];
int iden(int p){
	if(!fa[p]) return -1;
	if(tr[fa[p]][0]==p) return 0;
	if(tr[fa[p]][1]==p) return 1;
	return -1;
}
void rotate(int p){
	int f=fa[p],idf=iden(f),idp=iden(p);
	if(~idf) tr[fa[f]][idf]=p;
	fa[p]=fa[f];
	fa[tr[f][idp]=tr[p][idp^1]]=f;
	fa[tr[p][idp^1]=f]=p;
}
void splay(int p){
	while(~iden(p)){
		int f=fa[p];
		if(~iden(f)) rotate((iden(f)^iden(p))?p:f);
		rotate(p);
	}
}
void splayto(int p,int g){
	while(fa[p]!=g){
		int f=fa[p];
		if(fa[f]!=g) rotate((iden(f)^iden(p))?p:f);
		rotate(p);
	}
}
void link(int x,int y){
	//printf("link %d %d\n",x,y);
	if(!x||!y) return;
	trlink(x,y);splay(x);fa[x]=y;
	if(tr[x][1]&&(tr[x][1]>x)!=(x>y)) tr[x][1]=0;
	if(ft[y]&&(x>y)==(y>ft[y])) tr[y][1]=x;
}
void cut(int x,int y){
	//printf("cut %d %d\n",x,y);
	if(!x||!y) return;
	trcut(x,y);splay(x);
	if(fa[x]==y) fa[x]=0;
	else{
		splayto(y,x);
		fa[y]=fa[x];
		fa[x]=tr[x][0]=0;
	}
}
void output(){
	int lx=cl[rt],rx=cr[rt];
	trcut(lx,rt);trcut(rx,rt);
	printf("%lld\n",cur);
	trlink(lx,rt);trlink(rx,rt);
}
bool le(int x,int y){
	if(a[x]==a[y]) return x>y;
	return a[x]<a[y];
}
int nxt(int x){
	splay(x);
	int p=x,pos=x,nx=tr[p][0];
	while(nx){
		p=nx;
		if(le(p,x)) pos=p,nx=tr[p][0];
		else nx=tr[p][1];
	}
	splay(p);
	return pos;
}
void update(int x){
	int ls=cl[x],rs=cr[x];
	cut(ls,x);cut(rs,x);
	while(ft[x]&&le(ft[x],x)){
		int f=ft[x];
		splay(x);
		int t=tr[x][0]?nxt(x):f,ff=ft[t];
		if(t==x) break;
		if(x>f){cut(x,f);link(ls,f);cut(ls=t,ff);link(x,ff);}
		else{cut(x,f);link(rs,f);cut(rs=t,ff);link(x,ff);}
	}
	link(ls,x);link(rs,x);
	if(!ft[x]) rt=x;
}
int main(){
	n=read();rt=1;
	for(int i=1;i<n;++i) link(i+1,i);
	for(int i=1;i<=n;++i) a[i]=read(),update(i);
	q=read();
	while(q--){
		int x=read(),v=read();
		a[x]+=v;update(x);output();
	}
	return 0;
}
posted @ 2023-02-22 14:03  yyyyxh  阅读(202)  评论(0编辑  收藏  举报