树套树学习笔记

前言

其实是复习笔记,但是等于新学了。

前置知识

因为我比较懒惰,而且代码能力堪忧,所以我选择码量少的树状数组套主席树的写法。
所以前置知识有:树状数组,可持久化权值线段树(主席树)
这俩我都写过,详见之前的学习笔记。

树套树

您需要写一种数据结构(可参考题目标题),来维护一个有序数列,其中需要提供以下操作:

  1. 查询 \(k\) 在区间内的排名;
  2. 查询区间内排名为 \(k\) 的值;
  3. 修改某一位置上的数值;
  4. 查询 \(k\) 在区间内的前驱(前驱定义为严格小于 \(x\),且最大的数,若不存在输出 -2147483647);
  5. 查询 \(k\) 在区间内的后继(后继定义为严格大于 \(x\),且最小的数,若不存在输出 2147483647)。

对于一组元素,一个数的排名被定义为严格比它小的元素个数加一,而排名为 \(k\) 的数被定义为“将元素从小到大排序后排在第 \(k\) 位的元素值”。

是的这就是我们树套树干的事情,具体实现也很简单,只需要把两个数据结构的代码有脑地拼在一起即可。

对于每一个点开一个权值线段树,然后拿树状数组维护一下,修改的时候按照树状数组访问,然后按照权值线段树的修改方式修改节点。

具体而言,延续主席树的前缀和思想,不过这次我们先在树状数组上计算出来用到的节点,然后再进入主席树修改或者查询。
然后我们就可以查询第 \(k\) 小值和 \(k\) 在区间的排名,至于前驱后继就先查 \(k\) 的排名,再查 \(k+1/k-1\) 的值即可。
code:

#include<iostream>
#include<algorithm>
using namespace std;
const int N=5e5+50;
const int inf=2147483647;
struct node{
	int val,ls,rs;
}tr[N*100];
struct mdf{
	int a,b,c,d;
}q[N];
int rt[N],tem[N],tmp[N],cnt,num,tot;
int n,m,lsh[2*N],len,a[N];
int find(int x){
	return lower_bound(lsh+1,lsh+1+len,x)-lsh;
}
int lowbit(int x){
	return x&-x;
}
void pushup(int u){
	tr[u].val=tr[tr[u].ls].val+tr[tr[u].rs].val;
	return ;
}
//正常修改操作
void modify(int &u,int l,int r,int x,int k){
	if(!u) u=++tot;
	if(l==r){
		tr[u].val+=k;
		return ;
	}
	int mid=(l+r)>>1;
	if(x<=mid) modify(tr[u].ls,l,mid,x,k);
	else modify(tr[u].rs,mid+1,r,x,k);
	pushup(u);
	return ;
}
//找到要修改哪些节点
void add(int p,int k){
	for(int i=p;i<=n;i+=lowbit(i)) modify(rt[i],1,len,a[p],k);
}
//正常查询排名为 k 的值
//tem对应原来主席树中的 ls[now],tmp对应 ls[pre]
//然后统计所有的和才是原来的 tr[ls[now]]-tr[ls[pre]]
//访问左右儿子的时候记得把 now,pre 换过去,也就是 tem,tmp
int query1(int l,int r,int k){
	if(l==r){
		return l;
	}
	int mid=(l+r)>>1,sum=0;
	for(int i=1;i<=cnt;i++) sum+=tr[tr[tem[i]].ls].val;
	for(int i=1;i<=num;i++) sum-=tr[tr[tmp[i]].ls].val;
	if(k<=sum){
		for(int i=1;i<=cnt;i++) tem[i]=tr[tem[i]].ls;
		for(int i=1;i<=num;i++) tmp[i]=tr[tmp[i]].ls;
		return query1(l,mid,k);
	}else{
		for(int i=1;i<=cnt;i++) tem[i]=tr[tem[i]].rs;
		for(int i=1;i<=num;i++) tmp[i]=tr[tmp[i]].rs;
		return query1(mid+1,r,k-sum);
	}
}
//查询有哪些节点被访问了,并记录
int fnum(int l,int r,int k){
	cnt=num=0;
	for(int i=r;i;i-=lowbit(i)){
		tem[++cnt]=rt[i];
	}
	for(int i=l-1;i;i-=lowbit(i)){
		tmp[++num]=rt[i];
	}
	return query1(1,len,k);
}
//同上,因为是查询排名,所以递归右子树时加上左子树的和
int query2(int l,int r,int k){
	if(l==r) return 0;
	int mid=(l+r)>>1,sum=0;
	if(k<=mid){
		for(int i=1;i<=cnt;i++) tem[i]=tr[tem[i]].ls;
		for(int i=1;i<=num;i++) tmp[i]=tr[tmp[i]].ls;
		return query2(l,mid,k);
	}else{
		for(int i=1;i<=cnt;i++) sum+=tr[tr[tem[i]].ls].val,tem[i]=tr[tem[i]].rs;
		for(int i=1;i<=num;i++) sum-=tr[tr[tmp[i]].ls].val,tmp[i]=tr[tmp[i]].rs;
		return sum+query2(mid+1,r,k);
	}
}
//同上
int frnk(int l,int r,int k){
	cnt=num=0;
	for(int i=r;i;i-=lowbit(i)){
		tem[++cnt]=rt[i];
	}
	for(int i=l-1;i;i-=lowbit(i)){
		tmp[++num]=rt[i];
	}
	return query2(1,len,k)+1;
}
//查前驱,如果是最小的返回 0
int fpri(int l,int r,int k){
	int rk=frnk(l,r,k)-1;
	if(rk==0) return 0;
	return fnum(l,r,rk);
}
//查后继,如果是最大的返回 len+1
int fnxt(int l,int r,int k){
	if(k==len) return len+1;
	int rk=frnk(l,r,k+1);
	if(rk==r-l+2) return len+1;
	return fnum(l,r,rk);
}
int main(){
	cin>>n>>m;
	tot=cnt=num=0;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		lsh[++len]=a[i];
	}
	for(int i=1;i<=m;i++){
		cin>>q[i].a>>q[i].b>>q[i].c;
		if(q[i].a!=3) cin>>q[i].d;
		else lsh[++len]=q[i].c;
		if(q[i].a==4 || q[i].a==5) lsh[++len]=q[i].d;
	}
	//离散化
	sort(lsh+1,lsh+1+len);
	len=unique(lsh+1,lsh+1+len)-lsh-1;
	for(int i=1;i<=n;i++){
		a[i]=find(a[i]);
		add(i,1);
	}
	//依据题意
	lsh[0]=-inf;
	lsh[len+1]=inf;
	for(int i=1;i<=m;i++){
		if(q[i].a==1){
			q[i].d=find(q[i].d);
			cout<<frnk(q[i].b,q[i].c,q[i].d)<<'\n';
		}
		if(q[i].a==2){
			cout<<lsh[fnum(q[i].b,q[i].c,q[i].d)]<<'\n';
		}
		if(q[i].a==3){
			add(q[i].b,-1);
			a[q[i].b]=find(q[i].c);
			add(q[i].b,1);
		}
		if(q[i].a==4){
			q[i].d=find(q[i].d);
			cout<<lsh[fpri(q[i].b,q[i].c,q[i].d)]<<'\n';
		}
		if(q[i].a==5){
			q[i].d=find(q[i].d);
			cout<<lsh[fnxt(q[i].b,q[i].c,q[i].d)]<<'\n';
		}
	}
	return 0;
}
posted @ 2025-04-29 21:31  Tighnari  阅读(6)  评论(0)    收藏  举报