【学习笔记】FHQ-Treap

一、前言

在今年 NOIP 前学习了 FHQ-Treap(一种平衡树),现在来记一记。

二、定义

Treap=tree+heap。具体的说,Treap 所维护的值满足二叉搜索树的性质,另一个变量优先级满足堆的性质。优先级一般使一个随机数,这使得树的高度保持在 \(O(\log n)\) 水平,二叉搜索树的性质使树可以维护有序的集合或序列。因为 \(O(\log n)\) 的树高,Treap 的操作时间复杂度一般都是 \(O(\log n)\) 的。

FHQ-Treap 又名无旋 Treap,它通过旋转和分裂维护堆的性质。

三、基本操作

1.合并

合并函数 merge(int p,int q) 用于合并以 \(p\) 为根的树和以 \(q\) 为根的树。需要注意的是,合并函数传入的两个参数 \(p\)\(q\) 要求满足二叉搜索树的性质,因此在合并过程中我们只需维护堆的性质即可。

以小根堆为例(其实大根小根都无所谓):

il int merge(int p,int q){
	if(!p||!q){
		return p+q;
	}
	if(jian(p)<jian(q)){
		rs(p)=merge(rs(p),q);
		pushup(p);
		return p;
	}
	else{
		ls(q)=merge(p,ls(q));
		pushup(q);
		return q;
	}
}

2.分裂

分为按值分裂和按排名分裂。以按值分裂为例,函数 split(int id,int val,int &p,int &q)\(id\) 为根的树分成 \(p\)\(q\) 两棵树,其中 \(p\) 中的值都小于等于 \(val\)\(q\) 中的值都大于 \(val\)

il void split(int id,int val,int &p,int &q){
	if(!id){
		p=q=0;
		return ;
	}
	if(val(id)<=val){
		p=id;
		split(rs(id),val,rs(id),q);
	}
	else{
		q=id;
		split(ls(id),val,p,ls(id));
	}
	pushup(id);
}

其他操作就都很简单了。

四、例题

1.普通平衡树

板子题,维护一个有序的集合。具体讲讲每个操作。

插入

将树按插入的值 \(val\) 分裂,再新建一个值为 \(val\) 的点合并进去。

删除

通过两次分裂,将所有值为 \(val\) 的点搞到一棵树中。忽略这棵树的根节点进行合并即可。

查询排名

将树按 \(val-1\) 分裂,左边的树的大小再加一就是答案。

查询第 \(k\)

可以按照二叉搜索树的方式递归查询。

查询前驱

\(val-1\) 分裂,设左边大小为 \(res\),排名为 \(res\) 的就是前驱。

查询后继

\(val\) 分裂,设左边大小为 \(res\),排名为 \(res+1\) 的就是后继。

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define read(x){\
	char ch;\
	int fu=1;\
	while(!isdigit(ch=getchar()))\
		fu-=(ch=='-')<<1;\
	x=ch&15;\
	while(isdigit(ch=getchar()))\
		x=(x<<1)+(x<<3)+(ch&15);\
	x*=fu;\
}
using namespace std;
namespace cplx{bool begin;}
const int maxn=1e5+5;
int n;
struct node{
	int ls,rs,val,jian,sz;
	node(int ls=0,int rs=0,int val=0,int jian=0,int sz=0)
		:ls(ls),rs(rs),val(val),jian(jian),sz(sz){}
};
struct FHQtreap{
	#define ls(id) tr[id].ls
	#define rs(id) tr[id].rs
	#define val(id) tr[id].val
	#define jian(id) tr[id].jian
	#define sz(id) tr[id].sz
	int rt,tot;
	node tr[maxn];
	il int New(int val){
		tot++;
		val(tot)=val;
		jian(tot)=rand();
		sz(tot)=1;
		return tot;
	}
	il void pushup(int id){
		sz(id)=sz(ls(id))+sz(rs(id))+1;
	}
il void split(int id,int val,int &p,int &q){
	if(!id){
		p=q=0;
		return ;
	}
	if(val(id)<=val){
		p=id;
		split(rs(id),val,rs(id),q);
	}
	else{
		q=id;
		split(ls(id),val,p,ls(id));
	}
	pushup(id);
}
il int merge(int p,int q){
	if(!p||!q){
		return p+q;
	}
	if(jian(p)<jian(q)){
		rs(p)=merge(rs(p),q);
		pushup(p);
		return p;
	}
	else{
		ls(q)=merge(p,ls(q));
		pushup(q);
		return q;
	}
}
	il void insert(int val){
		int x,y;
		split(rt,val,x,y);
		rt=merge(merge(x,New(val)),y);
	}
	il void erase(int val){
		int x,y,z;
		split(rt,val,x,y);
		split(x,val-1,x,z);
		rt=merge(merge(x,merge(ls(z),rs(z))),y);
	}
	il int rnk(int val){
		int x,y;
		split(rt,val-1,x,y);
		int res=sz(x)+1;
		rt=merge(x,y);
		return res;
	}
	il int kth(int id,int k){
		if(sz(ls(id))==k-1){
			return val(id);
		}
		if(sz(ls(id))>=k){
			return kth(ls(id),k);
		}
		return kth(rs(id),k-sz(ls(id))-1);
	}
	il int kth(int val){
		return kth(rt,val);
	}
	il int pre(int val){
		int x,y;
		split(rt,val-1,x,y);
		int res=sz(x);
		rt=merge(x,y);
		return kth(res);
	}
	il int nxt(int val){
		int x,y;
		split(rt,val,x,y);
		int res=sz(x);
		rt=merge(x,y);
		return kth(res+1);
	}
	#undef ls
	#undef rs
	#undef val
	#undef jian
	#undef sz
}fhq;
namespace cplx{
	bool end;
	il double usdmem(){return (&begin-&end)/1048576.0;}
}
int main(){
	srand(time(0));
	read(n);
	while(n--){
		int opt,x;
		read(opt)read(x)
		switch(opt){
			case 1:{
				fhq.insert(x);
				break;
			}
			case 2:{
				fhq.erase(x);
				break;
			}
			case 3:{
				printf("%d\n",fhq.rnk(x));
				break;
			}
			case 4:{
				printf("%d\n",fhq.kth(x));
				break;
			}
			case 5:{
				printf("%d\n",fhq.pre(x));
				break;
			}
			default:{
				printf("%d\n",fhq.nxt(x));
				break;
			}
		}
	}
	return 0;
}

2.文艺平衡树

维护序列。考虑翻转区间 \([l,r]\),就是将 \([l,r]\) 中的每个节点的左右儿子都交换。打上懒标记即可。

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define read(x){\
	char ch;\
	int fu=1;\
	while(!isdigit(ch=getchar()))\
		fu-=(ch=='-')<<1;\
	x=ch&15;\
	while(isdigit(ch=getchar()))\
		x=(x<<1)+(x<<3)+(ch&15);\
	x*=fu;\
}
#define ls(id) tr[id].ls
#define rs(id) tr[id].rs
#define val(id) tr[id].val
#define jian(id) tr[id].jian
#define tag(id) tr[id].tag
#define sz(id) tr[id].sz
using namespace std;
namespace cplx{bool begin;}
const int maxn=1e5+5;
int n,m,rt,tot;
struct node{
	int ls,rs,val,sz,jian;
	bool tag;
}tr[maxn];
il int New(int val){
	tot++;
	val(tot)=val;
	sz(tot)=1;
	jian(tot)=rand();
	return tot;
}
il void pushup(int id){
	sz(id)=sz(ls(id))+sz(rs(id))+1;
}
il void pushdown(int id){
	if(tag(id)){
		swap(ls(id),rs(id));
		tag(ls(id))^=1,tag(rs(id))^=1;
		tag(id)=0;
	}
}
il void split(int id,int k,int &p,int &q){
	if(!id){
		p=q=0;
		return ;
	}
	pushdown(id);
	if(sz(ls(id))>=k){
		q=id;
		split(ls(id),k,p,ls(id));
	}	
	else{
		p=id;
		split(rs(id),k-sz(ls(id))-1,rs(id),q);
	}
	pushup(id);
}
il int merge(int p,int q){
	if(!p||!q){
		return p+q;
	}
	if(jian(p)<jian(q)){
		pushdown(p);
		rs(p)=merge(rs(p),q);
		pushup(p);
		return p;
	}
	pushdown(q);
	ls(q)=merge(p,ls(q));
	pushup(q);
	return q;
}
il void dfs(int id){
	if(!id){
		return ;
	}
	pushdown(id);
	dfs(ls(id));
	printf("%d ",val(id));
	dfs(rs(id));
}
namespace cplx{
	bool end;
	il double usdmem(){return (&begin-&end)/1048576.0;}
}
int main(){
	srand(time(0));
	read(n)read(m);
	for(int i=1;i<=n;i++){
		rt=merge(rt,New(i));
	}
	while(m--){
		int l,r;
		read(l)read(r);
		int x,y,z;
		split(rt,r,x,y);
		split(x,l-1,x,z);
		tag(z)^=1;
		rt=merge(merge(x,z),y);
	}
	dfs(rt);
	return 0;
}

3.[HNOI2002] 营业额统计

显然对于每一天,答案就是这一天的值和前驱后继的差的最小值。直接维护即可。

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define read(x){\
	char ch;\
	int fu=1;\
	while(!isdigit(ch=getchar()))\
		fu-=(ch=='-')<<1;\
	x=ch&15;\
	while(isdigit(ch=getchar()))\
		x=(x<<1)+(x<<3)+(ch&15);\
	x*=fu;\
}
using namespace std;
namespace cplx{bool begin;}
const int maxn=(1<<15)+5;
const int inf=0x3f3f3f3f;
int n,rt,tot,ls[maxn],rs[maxn];
int val[maxn],sz[maxn],jian[maxn];
il int New(int x){
	tot++;
	val[tot]=x;
	sz[tot]=1;
	jian[tot]=rand();
	return tot;
}
il void pushup(int id){
	sz[id]=sz[ls[id]]+sz[rs[id]]+1;
}
il int merge(int p,int q){
	if(!p||!q){
		return p+q;
	}
	if(jian[p]<jian[q]){
		rs[p]=merge(rs[p],q);
		pushup(p);
		return p;
	}
	ls[q]=merge(p,ls[q]);
	pushup(q);
	return q;
}
il void split(int id,int x,int &p,int &q){
	if(!id){
		p=q=0;
		return ;
	}
	if(val[id]<=x){
		p=id;
		split(rs[id],x,rs[id],q);
	}
	else{
		q=id;
		split(ls[id],x,p,ls[id]);
	}
	pushup(id);
}
il void insert(int x){
	int p,q;
	split(rt,x,p,q);
	rt=merge(merge(p,New(x)),q);
}
il int rnk(int x){
	int p,q;
	split(rt,x-1,p,q);
	int res=sz[p]+1;
	rt=merge(p,q);
	return res;
}
il int kth(int id,int k){
	if(sz[ls[id]]==k-1){
		return val[id];
	}
	if(sz[ls[id]]>=k){
		return kth(ls[id],k);
	}
	return kth(rs[id],k-sz[ls[id]]-1);
}
il int pre(int x){
	int p,q;
	split(rt,x-1,p,q);
	int res=sz[p];
	rt=merge(p,q);
	return kth(rt,res);
}
il int nxt(int x){
	int p,q;
	split(rt,x-1,p,q);
	int res=kth(q,1);
	rt=merge(p,q);
	return res;
}
namespace cplx{
	bool end;
	il double usdmem(){return (&begin-&end)/1048576.0;}
}
int main(){
	read(n);
	insert(-inf),insert(inf);
	ll ans=0;
	for(int i=1,x;i<=n;i++){
		read(x);
		if(i==1){
			ans+=x;
		}
		else{
			ans+=min(x-pre(x),nxt(x)-x);
		}
		insert(x);
	}
	printf("%lld",ans);
	return 0;
}

4.[HNOI2004] 宠物收养场

依然是求前驱后继。要注意的是,需要记录当前平衡树内维护的是宠物还是领养者。

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define read(x){\
	char ch;\
	int fu=1;\
	while(!isdigit(ch=getchar()))\
		fu-=(ch=='-')<<1;\
	x=ch&15;\
	while(isdigit(ch=getchar()))\
		x=(x<<1)+(x<<3)+(ch&15);\
	x*=fu;\
}
using namespace std;
namespace cplx{bool begin;}
const int maxn=8e4+5,mod=1e6;
const ll inf=0x3f3f3f3f3f3f3f3f;
int n,rt,tot,ls[maxn],rs[maxn];
int sz[maxn],jian[maxn];
ll zhi[maxn];
il int New(ll val){
	zhi[++tot]=val;
	jian[tot]=rand();
	sz[tot]=1;
	return tot;
}
il void pushup(int id){
	sz[id]=sz[ls[id]]+sz[rs[id]]+1;
}
il void split(int id,ll val,int &p,int &q){
	if(!id){
		p=q=0;
		return ;
	}
	if(zhi[id]<=val){
		p=id;
		split(rs[id],val,rs[id],q);
	}
	else{
		q=id;
		split(ls[id],val,p,ls[id]);
	}
	pushup(id);
}
il int merge(int p,int q){
	if(!p||!q){
		return p+q;
	}
	if(jian[p]<jian[q]){
		rs[p]=merge(rs[p],q);
		pushup(p);
		return p;
	}
	ls[q]=merge(p,ls[q]);
	pushup(q);
	return q;
}
il void insert(ll val){
	int x,y;
	split(rt,val,x,y);
	rt=merge(merge(x,New(val)),y);
}
il void erase(ll val){
	int x,y,z;
	split(rt,val,x,y);
	split(x,val-1,x,z);
	rt=merge(merge(x,merge(ls[z],rs[z])),y);
}
il ll kth(int id,int k){
	if(sz[ls[id]]==k-1){
		return zhi[id];
	}
	if(sz[ls[id]]>=k){
		return kth(ls[id],k);
	}
	return kth(rs[id],k-sz[ls[id]]-1);
}
il ll pre(ll val){
	int x,y;
	split(rt,val,x,y);
	ll res=kth(x,sz[x]);
	rt=merge(x,y);
	return res;
}
il ll nxt(ll val){
	int x,y;
	split(rt,val-1,x,y);
	ll res=kth(y,1);
	rt=merge(x,y);
	return res;
}
namespace cplx{
	bool end;
	il double usdmem(){return (&begin-&end)/1048576.0;}
}
int main(){
	srand(time(0));
	insert(-inf),insert(inf);
	ll ans=0;
	read(n);
	bool flag=0;
	while(n--){
		int opt;
		ll a;
		read(opt)read(a);
		if(sz[rt]==2||flag==opt){
			insert(a);
			flag=opt;
		}
		else{
			ll x=pre(a),y=nxt(a);
			(ans+=min(a-x,y-a))%=mod;
			erase(a-x<=y-a?x:y);
		}
	}
	printf("%lld",ans);
	return 0;
}

5.「NOI 2004」郁闷的出纳员

整体修改 + 查询第 \(k\) 小,打懒标记即可。

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define read(x){\
	char ch;\
	int fu=1;\
	while(!isdigit(ch=getchar()))\
		fu-=(ch=='-')<<1;\
	x=ch&15;\
	while(isdigit(ch=getchar()))\
		x=(x<<1)+(x<<3)+(ch&15);\
	x*=fu;\
}
using namespace std;
namespace cplx{bool begin;}
const int maxn=1e5+5;
int n,m,rt,tot,ls[maxn],rs[maxn];
int zhi[maxn],jian[maxn];
int tag[maxn],sz[maxn];
il int New(int val){
	zhi[++tot]=val;
	jian[tot]=rand();
	sz[tot]=1;
	return tot;
}
il void pushup(int id){
	sz[id]=sz[ls[id]]+sz[rs[id]]+1;
}
il void pushdown(int id){
	if(tag[id]){
		tag[ls[id]]+=tag[id];
		tag[rs[id]]+=tag[id];
		zhi[ls[id]]+=tag[id];
		zhi[rs[id]]+=tag[id];
		tag[id]=0;
	}
}
il void split(int id,int val,int &p,int &q){
	if(!id){
		p=q=0;
		return ;
	}
	pushdown(id);
	if(zhi[id]<=val){
		p=id;
		split(rs[id],val,rs[id],q);
	}
	else{
		q=id;
		split(ls[id],val,p,ls[id]);
	}
	pushup(id);
}
il int merge(int p,int q){
	if(!p||!q){
		return p+q;
	}
	if(jian[p]<jian[q]){
		pushdown(p);
		rs[p]=merge(rs[p],q);
		pushup(p);
		return p;
	}
	pushdown(q);
	ls[q]=merge(p,ls[q]);
	pushup(q);
	return q;
}
il void insert(int val){
	int x,y;
	split(rt,val,x,y);
	rt=merge(merge(x,New(val)),y);
}
il void erase(int val){
	int x,y,z;
	split(rt,val,x,y);
	split(x,val-1,x,z);
	rt=merge(merge(x,merge(ls[z],rs[z])),y);
}
il int kth(int id,int k){
	if(sz[ls[id]]==k-1){
		return zhi[id];
	}
	pushdown(id);
	if(sz[ls[id]]>=k){
		return kth(ls[id],k);
	}
	return kth(rs[id],k-sz[ls[id]]-1);
}
namespace cplx{
	bool end;
	il double usdmem(){return (&begin-&end)/1048576.0;}
}
int main(){
	srand(time(0));
	read(n)read(m);
	int ans=0;
	while(n--){
		char opt;
		int x;
		scanf(" %c",&opt);
		read(x);
		switch(opt){
			case 'I':{
				if(x>=m){
					insert(x);
				}
				break;
			}
			case 'A':{
				if(sz[rt]){
					tag[rt]+=x,zhi[rt]+=x;
				}
				break;
			}
			case 'S':{
				if(sz[rt]){
					tag[rt]-=x,zhi[rt]-=x;
					int val=kth(rt,1);
					while(val<m){
						erase(val),ans++;
						if(sz[rt]){
							val=kth(rt,1);
						}
						else{
							break;
						}
					}
				}
				break;
			}
			default:{
				printf("%d\n",x>sz[rt]?-1:kth(rt,sz[rt]-x+1));
				break;
			}
		}
	}
	printf("%d",ans);
	return 0;
}

6.[JSOI2008]火星人prefix

考虑用平衡树维护哈希。那么对于修改操作,就是一个区间加和区间乘的操作了。询问二分即可。

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define read(x){\
	char ch;\
	int fu=1;\
	while(!isdigit(ch=getchar()))\
		fu-=(ch=='-')<<1;\
	x=ch&15;\
	while(isdigit(ch=getchar()))\
		x=(x<<1)+(x<<3)+(ch&15);\
	x*=fu;\
}
#define ull unsigned ll
using namespace std;
namespace cplx{bool begin;}
const int maxn=1e5+5;
const ull bas1=13331;
const ll bas2=13327,mod2=2.5e9+1;
int n,m,rt,tot,sz[maxn],ls[maxn],rs[maxn],jian[maxn];
ull pw1[maxn],ha1[maxn],tgj1[maxn],tgc1[maxn];
ll pw2[maxn],ha2[maxn],tgj2[maxn],tgc2[maxn];
char s[maxn],zhi[maxn];
il void pushup(int id){
	sz[id]=sz[ls[id]]+sz[rs[id]]+1;
}
il void pushdown(int id){
	if(tgc1[id]!=1){
		if(ls[id]){
			tgc1[ls[id]]*=tgc1[id];
			tgj1[ls[id]]*=tgc1[id];
			ha1[ls[id]]*=tgc1[id];
		}
		if(rs[id]){
			tgc1[rs[id]]*=tgc1[id];
			tgj1[rs[id]]*=tgc1[id];
			ha1[rs[id]]*=tgc1[id];
		}
		tgc1[id]=1;
	}
	if(tgc2[id]!=1){
		if(ls[id]){
			(tgc2[ls[id]]*=tgc2[id])%=mod2;
			(tgj2[ls[id]]*=tgc2[id])%=mod2;
			(ha2[ls[id]]*=tgc2[id])%=mod2;
		}
		if(rs[id]){
			(tgc2[rs[id]]*=tgc2[id])%=mod2;
			(tgj2[rs[id]]*=tgc2[id])%=mod2;
			(ha2[rs[id]]*=tgc2[id])%=mod2;
		}
		tgc2[id]=1;
	}
	if(tgj1[id]){
		if(ls[id]){
			tgj1[ls[id]]+=tgj1[id];
			ha1[ls[id]]+=tgj1[id];
		}
		if(rs[id]){
			tgj1[rs[id]]+=tgj1[id];
			ha1[rs[id]]+=tgj1[id];
		}
		tgj1[id]=0;
	}
	if(tgj2[id]){
		if(ls[id]){
			(tgj2[ls[id]]+=tgj2[id])%=mod2;
			(ha2[ls[id]]+=tgj2[id])%=mod2;
		}
		if(rs[id]){
			(tgj2[rs[id]]+=tgj2[id])%=mod2;
			(ha2[rs[id]]+=tgj2[id])%=mod2;
		}
		tgj2[id]=0;
	}
}
il void split(int id,int k,int &p,int &q){
	if(!id){
		p=q=0;
		return ;
	}
	pushdown(id);
	if(sz[ls[id]]<k){
		p=id;
		split(rs[id],k-sz[ls[id]]-1,rs[id],q);
	}
	else{
		q=id;
		split(ls[id],k,p,ls[id]);
	}
	pushup(id);
}
il int merge(int p,int q){
	if(!p||!q){
		return p+q;
	}
	if(jian[p]<jian[q]){
		pushdown(p);
		rs[p]=merge(rs[p],q);
		pushup(p);
		return p;
	}
	pushdown(q);
	ls[q]=merge(p,ls[q]);
	pushup(q);
	return q;
}
il int kth(int id,int k){
	if(sz[ls[id]]==k-1){
		return id;
	}
	pushdown(id);
	if(sz[ls[id]]<k){
		return kth(rs[id],k-sz[ls[id]]-1);
	}
	return kth(ls[id],k);
}
il void upd(int pos,char val){
	int x,y,z;
	split(rt,pos-1,x,y);
	z=kth(y,1);
	tgj1[y]+=pw1[pos-1]*(val-zhi[z]);
	ha1[y]+=pw1[pos-1]*(val-zhi[z]);
	(tgj2[y]+=(val-zhi[z]+mod2)%mod2*pw2[pos-1])%=mod2;
	(ha2[y]+=(val-zhi[z]+mod2)%mod2*pw2[pos-1])%=mod2;
	zhi[z]=val;
	rt=merge(x,y);
}
il void insert(int pos,char val){
	zhi[++tot]=val;
	sz[tot]=1;
	jian[tot]=rand();
	tgc1[tot]=tgc2[tot]=1;
	int x,y,z;
	split(rt,pos,x,y);
	z=kth(x,pos);
	ha1[tot]=ha1[z]+pw1[pos]*val;
	ha2[tot]=(ha2[z]+pw2[pos]*val)%mod2;
	if(y){
		tgc1[y]*=bas1;
		(tgc2[y]*=bas2)%=mod2;
		tgj1[y]=(tgj1[y]-ha1[z])*bas1+ha1[tot];
		tgj2[y]=((tgj2[y]-ha2[z]+mod2)%mod2*bas2+ha2[tot])%mod2;
		ha1[y]=(ha1[y]-ha1[z])*bas1+ha1[tot];
		ha2[y]=((ha2[y]-ha2[z]+mod2)%mod2*bas2+ha2[tot])%mod2;
	}
	rt=merge(merge(x,tot),y);
}
namespace cplx{
	bool end;
	il double usdmem(){return (&begin-&end)/1048576.0;}
}
int main(){
	srand(time(0));
	pw1[0]=pw2[0]=1;
	for(int i=1;i<=1e5;i++){
		pw1[i]=pw1[i-1]*bas1;
		pw2[i]=pw2[i-1]*bas2%mod2;
	}
	scanf(" %s",s+1);
	n=strlen(s+1);
	rt=tot=1;
	sz[rt]=1,jian[rt]=rand();
	tgc1[rt]=tgc2[rt]=1;
	for(int i=1;i<=n;i++){
		insert(i,s[i]);
	}
	read(m);
	while(m--){
		char opt;
		scanf(" %c",&opt);
		switch(opt){
			case 'Q':{
				int x,y,p,q;
				read(x)read(y);
				if(x>y){
					swap(x,y);
				}
				p=kth(rt,x);
				q=kth(rt,y);
				ull hx1=ha1[p],hy1=ha1[q];
				ll hx2=ha2[p],hy2=ha2[q];
				int l=0,r=sz[rt]-y;
				while(l<r){
					int mid=(l+r+1)>>1;
					p=kth(rt,x+mid);
					q=kth(rt,y+mid);
					if((ha1[p]-hx1)*pw1[y-x]==ha1[q]-hy1&&(ha2[p]-hx2+mod2)%mod2*pw2[y-x]%mod2==(ha2[q]-hy2+mod2)%mod2){
						l=mid;
					}
					else{
						r=mid-1;
					}
				}
				printf("%d\n",l);
				break;
			}
			case 'R':{
				int x;
				char val;
				read(x);
				scanf(" %c",&val);
				upd(x+1,val);
				break;
			}
			default:{
				int x;
				char val;
				read(x);
				scanf(" %c",&val);
				insert(x+1,val);
				break;
			}
		}
	}
	return 0;
}

7.[Tjoi2013]最长上升子序列

考虑 DP,设 \(dp_i\) 表示以 \(i\) 结尾的最长上升子序列长度。有方程:

\[dp_i=\max_{j<i\land a_j<a_i} dp_j \]

因为是从小到大插入,我们就只用维护 \(j<i\) 的限制就行了。用平衡树维护序列即可。

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define read(x){\
	char ch;\
	int fu=1;\
	while(!isdigit(ch=getchar()))\
		fu-=(ch=='-')<<1;\
	x=ch&15;\
	while(isdigit(ch=getchar()))\
		x=(x<<1)+(x<<3)+(ch&15);\
	x*=fu;\
}
using namespace std;
namespace cplx{bool begin;}
const int maxn=1e5+5;
int n,rt,tot,sz[maxn],zhi[maxn],mx[maxn],jian[maxn],ls[maxn],rs[maxn];
il void pushup(int id){
	mx[id]=max({zhi[id],mx[ls[id]],mx[rs[id]]});
	sz[id]=sz[ls[id]]+sz[rs[id]]+1;
}
il void split(int id,int k,int &p,int &q){
	if(!id){
		p=q=0;
		return ;
	}
	if(sz[ls[id]]<k){
		p=id;
		split(rs[id],k-sz[ls[id]]-1,rs[id],q);
	}
	else{
		q=id;
		split(ls[id],k,p,ls[id]);
	}
	pushup(id);
}
il int merge(int p,int q){
	if(!p||!q){
		return p+q;
	}
	if(jian[p]<jian[q]){
		rs[p]=merge(rs[p],q);
		pushup(p);
		return p;
	}
	ls[q]=merge(p,ls[q]);
	pushup(q);
	return q;
}
namespace cplx{
	bool end;
	il double usdmem(){return (&begin-&end)/1048576.0;}
}
int main(){
	read(n);
	srand(time(0));
	for(int i=1,p,x,y;i<=n;i++){
		read(p);
		split(rt,p,x,y);
		sz[++tot]=1,jian[tot]=rand();
		mx[tot]=zhi[tot]=mx[x]+1;
		rt=merge(merge(x,tot),y);
		printf("%d\n",mx[rt]);
	}
	return 0;
}

8.二逼平衡树

线段树套平衡树。

在每个线段树的节点上维护一个平衡树,维护这个区间中的值。逐一分析每个操作。

查询排名

考虑排名的本质,就是比 \(val\) 小的值的数量加一。在线段树上将区间拆分成 \(O(\log n)\) 个,再在每个区间中查询累加答案即可。

查询第 \(k\)

这不好直接查,二分即可。

单点修改

在线段树上每个相关的节点修改即可。

查询前驱

前驱就是比 \(val\) 小的值中最大的值。在每个区间内查询并取 \(\max\) 即可。

查询后继

类似前驱。

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define read(x){\
	char ch;\
	int fu=1;\
	while(!isdigit(ch=getchar()))\
		fu-=(ch=='-')<<1;\
	x=ch&15;\
	while(isdigit(ch=getchar()))\
		x=(x<<1)+(x<<3)+(ch&15);\
	x*=fu;\
}
#define lid id<<1
#define rid id<<1|1
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
const int maxn=5e4+5,inf=0x3f3f3f3f;
int n,m,a[maxn],rt[maxn<<2];
namespace fhq{
	int tot,zhi[maxn*20],sz[maxn*20];
	int ls[maxn*20],rs[maxn*20],jian[maxn*20];
	int ljt[maxn*20],top;
	il int New(int val){
		int p=top?ljt[top--]:++tot;
		zhi[p]=val;
		jian[p]=rand();
		sz[p]=1;
		ls[p]=rs[p]=0;
		return p;
	}
	il void pushup(int id){
		sz[id]=sz[ls[id]]+sz[rs[id]]+1;
	}
	il void split(int id,int val,int &p,int &q){
		if(!id){
			p=q=0;
			return ;
		}
		if(zhi[id]<=val){
			p=id;
			split(rs[id],val,rs[id],q);
		}
		else{
			q=id;
			split(ls[id],val,p,ls[id]);
		}
		pushup(id);
	}
	il int merge(int p,int q){
		if(!p||!q){
			return p+q;
		}
		if(jian[p]<jian[q]){
			rs[p]=merge(rs[p],q);
			pushup(p);
			return p;
		}
		ls[q]=merge(p,ls[q]);
		pushup(q);
		return q;
	}
	il int rnk(int &id,int val){
		int x,y;
		split(id,val-1,x,y);
		int res=sz[x]+1;
		id=merge(x,y);
		return res;
	}
	il void insert(int &id,int val){
		int x,y;
		split(id,val,x,y);
		id=merge(merge(x,New(val)),y);
	}
	il void del(int &id,int val){
		int x,y,z;
		split(id,val,x,y);
		split(x,val-1,x,z);
		ljt[++top]=z;
		id=merge(merge(x,merge(ls[z],rs[z])),y);
	}
	il int kth(int id,int k){
		if(sz[ls[id]]==k-1){
			return zhi[id];
		}
		if(sz[ls[id]]>=k){
			return kth(ls[id],k);
		}
		return kth(rs[id],k-sz[ls[id]]-1);
	}
	il int qanq(int &id,int val){
		int x,y;
		split(id,val-1,x,y);
		int res=sz[x]?kth(x,sz[x]):-inf;
		id=merge(x,y);
		return res;
	}
	il int houj(int &id,int val){
		int x,y;
		split(id,val,x,y);
		int res=sz[y]?kth(y,1):inf;
		id=merge(x,y);
		return res;
	}
}
il void build(int id,int l,int r){
	for(int i=l;i<=r;i++){
		fhq::insert(rt[id],a[i]);
	}
	if(l==r){
		return ;
	}
	int mid=(l+r)>>1;
	build(lid,l,mid);
	build(rid,mid+1,r);
}
il int qrnk(int id,int L,int R,int l,int r,int val){
	if(L>=l&&R<=r){
		return fhq::rnk(rt[id],val);
	}
	int mid=(L+R)>>1,res=0;
	if(l<=mid){
		res+=qrnk(lid,L,mid,l,r,val)-1;
	}
	if(r>mid){
		res+=qrnk(rid,mid+1,R,l,r,val)-1;
	}
	return res+1;
}
il void upd(int id,int l,int r,int pos,int val){
	fhq::del(rt[id],a[pos]);
	fhq::insert(rt[id],val);
	if(l==r){
		return ;
	}
	int mid=(l+r)>>1;
	if(pos<=mid){
		upd(lid,l,mid,pos,val);
	}
	else{
		upd(rid,mid+1,r,pos,val);
	}
}
il int qqanq(int id,int L,int R,int l,int r,int val){
	if(L>=l&&R<=r){
		return fhq::qanq(rt[id],val);
	}
	int mid=(L+R)>>1,res=-inf;
	if(l<=mid){
		res=max(res,qqanq(lid,L,mid,l,r,val));
	}
	if(r>mid){
		res=max(res,qqanq(rid,mid+1,R,l,r,val));
	}
	return res;
}
il int qhouj(int id,int L,int R,int l,int r,int val){
	if(L>=l&&R<=r){
		return fhq::houj(rt[id],val);
	}
	int mid=(L+R)>>1,res=inf;
	if(l<=mid){
		res=min(res,qhouj(lid,L,mid,l,r,val));
	}
	if(r>mid){
		res=min(res,qhouj(rid,mid+1,R,l,r,val));
	}
	return res;
}
namespace cplx{
	bool end;
	il double usdmem(){return (&begin-&end)/1048576.0;}
}
int main(){
	srand(time(0));
	read(n)read(m);
	for(int i=1;i<=n;i++){
		read(a[i]);
	}
	build(1,1,n);
	while(m--){
		int opt;
		read(opt);
		switch(opt){
			case 1:{
				int l,r,val;
				read(l)read(r)read(val);
				printf("%d\n",qrnk(1,1,n,l,r,val));
				break;
			}
			case 2:{
				int l,r,k;
				read(l)read(r)read(k);
				int L=-inf,R=inf;
				while(L<R){
					int mid=(L+R+1)>>1;
					if(qrnk(1,1,n,l,r,mid)<=k){
						L=mid;
					}
					else{
						R=mid-1;
					}
				}
				printf("%d\n",L);
				break;
			}
			case 3:{
				int pos,val;
				read(pos)read(val);
				upd(1,1,n,pos,val);
				a[pos]=val;
				break;
			}
			case 4:{
				int l,r,val;
				read(l)read(r)read(val);
				printf("%d\n",qqanq(1,1,n,l,r,val));
				break;
			}
			default:{
				int l,r,val;
				read(l)read(r)read(val);
				printf("%d\n",qhouj(1,1,n,l,r,val));
				break;
			}
		}
	}
	return 0;
}
}
int main(){return asbt::main();}

9.星系探索

考虑给树建一个欧拉序,对于点 \(u\),在进入 \(u\) 的子树时将 \(u\) 加入序列,记位置为 \(dfn_u\);出 \(u\) 的子树是再将 \(u\) 加入序列,记位置为 \(low_u\)

如果在 \(dfn_u\) 记录 \(u\) 的权值,在 \(low_u\) 记录 \(u\) 的权值的负值,那么对于节点 \(u\),欧拉序上 \([1,dfn_u]\) 的和就是根到 \(u\) 的权值之和。

子树加是简单的,考虑子树移动。对应到区间上,其实就是区间平移。那么看起来似乎就很简单了。问题在于,平移后每个节点的排名可能会改变,要再按排名分裂会出问题。考虑对每个节点,再记录它的父亲节点 \(fa\)。那么一个节点当前的排名就可以跳 \(fa\) 来求了。

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define read(x){\
	char ch;\
	int fu=1;\
	while(!isdigit(ch=getchar()))\
		fu-=(ch=='-')<<1;\
	x=ch&15;\
	while(isdigit(ch=getchar()))\
		x=(x<<1)+(x<<3)+(ch&15);\
	x*=fu;\
}
#define int ll
#define pb push_back
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
const int maxn=2e5+5;
int n,m,dfn[maxn],low[maxn];
int rt,tot,ls[maxn],rs[maxn];
int fa[maxn],zhi[maxn],sum[maxn];
int sz[maxn],jian[maxn],a[maxn];
int col[maxn],tcl[maxn],tag[maxn];
vector<int> e[maxn];
il int New(int x,int y){
	jian[++tot]=rand();
	sum[tot]=zhi[tot]=x;
	tcl[tot]=col[tot]=y;
	sz[tot]=1,fa[tot]=0;
	return tot;
}
il void pushup(int id){
	sum[id]=sum[ls[id]]+sum[rs[id]]+zhi[id];
	tcl[id]=tcl[ls[id]]+tcl[rs[id]]+col[id];
	sz[id]=sz[ls[id]]+sz[rs[id]]+1;
}
il void pushdown(int id){
	if(tag[id]){
		tag[ls[id]]+=tag[id];
		tag[rs[id]]+=tag[id];
		zhi[ls[id]]+=col[ls[id]]*tag[id];
		zhi[rs[id]]+=col[rs[id]]*tag[id];
		sum[ls[id]]+=tag[id]*tcl[ls[id]];
		sum[rs[id]]+=tag[id]*tcl[rs[id]];
		tag[id]=0;
	}
}
il void split(int id,int k,int &p,int &q){
	if(!id){
		p=q=0;
		return ;
	}
	pushdown(id);
	if(sz[ls[id]]<k){
		p=id;
		split(rs[id],k-sz[ls[id]]-1,rs[id],q);
		fa[rs[id]]=id;
	}
	else{
		q=id;
		split(ls[id],k,p,ls[id]);
		fa[ls[id]]=id;
	}
	pushup(id);
}
il int merge(int p,int q){
	if(!p||!q){
		return p+q;
	}
	if(jian[p]<jian[q]){
		pushdown(p);
		rs[p]=merge(rs[p],q);
		fa[rs[p]]=p;
		pushup(p);
		return p;
	}
	pushdown(q);
	ls[q]=merge(p,ls[q]);
	fa[ls[q]]=q;
	pushup(q);
	return q;
}
il void dfs(int u,int fth){
	rt=merge(rt,dfn[u]=New(a[u],1));
	fa[rt]=0;
	for(int v:e[u]){
		dfs(v,u);
	}
	rt=merge(rt,low[u]=New(-a[u],-1));
	fa[rt]=0;
}
il int rnk(int id){
	int res=sz[ls[id]]+1;
	while(fa[id]){
		if(id==rs[fa[id]]){
			res+=sz[ls[fa[id]]]+1;
		}
		id=fa[id];
	}
	return res;
}
namespace cplx{
	bool end;
	il double usdmem(){return (&begin-&end)/1048576.0;}
}
signed main(){
	read(n);
	for(int i=2,x;i<=n;i++){
		read(x);
		e[x].pb(i);
	}
	for(int i=1;i<=n;i++){
		read(a[i]);
	}
	dfs(1,0);
	read(m);
	while(m--){
		char opt;
		scanf(" %c",&opt);
		switch(opt){
			case 'Q':{
				int u,x,y;
				read(u);
				split(rt,rnk(dfn[u]),x,y);
				fa[x]=fa[y]=0;
				printf("%lld\n",sum[x]);
				rt=merge(x,y);
				fa[rt]=0;
				break;
			}
			case 'C':{
				int u,v;
				read(u)read(v);
				int l=rnk(dfn[u]),r=rnk(low[u]);
				int tv=rnk(dfn[v]);
				int x,y,z;
				split(rt,r,x,y);
				fa[x]=fa[y]=0;
				split(x,l-1,x,z);
				fa[x]=fa[z]=0;
				if(tv<l){
					int w;
					split(x,tv,x,w);
					fa[x]=fa[w]=0;
					rt=merge(merge(x,z),merge(w,y));
					fa[rt]=0;
				}
				else{
					int w;
					split(y,tv-r,w,y);
					rt=merge(merge(x,w),merge(z,y));
					fa[rt]=0;
				}
				break;
			}
			default:{
				int u,val;
				read(u)read(val);
				int l=rnk(dfn[u]),r=rnk(low[u]);
				int x,y,z;
				split(rt,r,x,y);
				split(x,l-1,x,z);
				tag[z]+=val,zhi[z]+=col[z]*val;
				sum[z]+=val*tcl[z];
				rt=merge(merge(x,z),y);
				break;
			}
		}
	}
	return 0;
}
}
signed main(){return asbt::main();}
posted @ 2025-02-21 17:53  zhangxy__hp  阅读(69)  评论(0)    收藏  举报