【模板】树套树

在这里先给出一道模板题

【XSY2685】【LG3380】【BZOJ3196】【TYVJ1730】二逼平衡树


Description

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

1.查询k在区间内的排名(一个数的排名是小于这个数的个数+1)

2.查询区间内排名为k的值

3.修改某一位值上的数值

4.查询k在区间内的前驱(前驱定义为小于x,且最大的数)

5.查询k在区间内的后继(后继定义为大于x,且最小的数)


Input

第一行两个数n,m表示长度为n的有序序列和m个操作

第二行有n个数,表示有序序列

下面有m行,opt表示操作标号

opt=1则为操作1,之后有三个数l,r,k表示查询k在区间[l,r]的排名

opt=2则为操作2,之后有三个数l,r,k表示查询区间[l,r]内排名为k的数

opt=3则为操作3,之后有两个数pos,k表示将pos位置的数修改为k

opt=4则为操作4,之后有三个数l,r,k表示查询区间[l,r]k的前驱

opt=5则为操作5,之后有三个数l,r,k表示查询区间[l,r]k的后继


Output

对于操作1,2,4,5各输出一行,表示查询结果


SampleInput

9 6
4 2 2 1 9 4 0 1 1
2 1 4 3
3 4 10
2 1 4 3
1 2 5 9
4 3 9 5
5 2 8 5


SampleOutput

2
4
3
4
9


HINT

1.nm的数据范围:n,m50000

2 序列中每个数的数据范围:[0,108]

3.虽然原题没有,但事实上5操作的k可能为负数

4.保证答案一定存在


题解


一、这道题目在说什么?

给你一个有序序列,需要实现:

  1. 区间查询k排名
  2. 区间查询排名k的值
  3. 单点修改权值
  4. 区间查询k前驱
  5. 区间查询k后继

二、这道题目怎么做?

这道题,涉及到了区间查询以及单点修改,我们反应过来:这是一道待修区间K大的题目。
我们可爱的线段树能够实现单点修改和区间修改,但是面对区间K大就无能为力
我们精简的主席树和功能强大的平衡树可以完成区间K大的任务,但是不能完成待修的任务

于是我们不得不放弃普通的线段树或者平衡树做法,这时,就要用树套树做法了。

我们在外层建一棵线段树,完成单点修改的任务
再在每个线段树节点建一棵平衡树(可以是treap,也可以是splay,也可以是另外各种可以实现区间询问的平衡树)
每次区间询问的时候,就调用线段树节点中的平衡树完成查询,在这个时候,平衡树对应的就是一个区间,而不是一整个线段树

在这里我用的是fhq treap,个人认为fhq treap容易理解,好打

在这里就不多讲fhq treap 的代码部分,都是板子,就只讲线段树的部分

为了方便,我将fhq treap封装成一个struct,需要的可以看看


三、代码实现


1. 区间查询值k的排名

我们在线段树中找到在询问区间内的节点,然后调用平衡树查询在节点的平衡树中的k排名

然后我们考虑一下,如果每次都查询的是小于等于k的数的个数,那么合并的时候,可能会出现很多个等于k的值,也就是说,这样求出的排名可能会重复多出一些

于是我们将queryrank定义为寻找小于k的数的个数,在最后的时候再+1

在这里,我们看看线段树在遍历的时候的小细节,我们可以分为3

  1. 当整个区间都在左子树,返回左子树的查询
  2. 当整个区间都在右子树,返回右子树的查询
  3. 区间横跨左右子树,返回两个子树的查询和,不过左子树的查询区间要变成[ql,mid],右子树变成[mid+1,qr]
int queryrank(int k,int l,int r,int ql,int qr,int val)//询问在[ql,qr]区间内小于val的数的个数
{
	if(ql<=l&&r<=qr)return a[k].rank(a[k].rt,val)-1;//在询问区间内,调用平衡树,记得-1
	int ans=0;
	int mid=(l+r)>>1;
	if(qr<=mid)ans=queryrank(k<<1,l,mid,ql,qr,val);//整个区间都在左子树
	else if(ql>mid)ans=queryrank(k<<1|1,mid+1,r,ql,qr,val);//整个区间都在右子树
	else ans=queryrank(k<<1,l,mid,ql,mid,val)+queryrank(k<<1|1,mid+1,r,mid+1,qr,val);//横跨左右子树
	return ans;
}

2.查询排名为k的值

这个操作不能树套树中实现,因为不知道怎么将答案合并起来,于是我们考虑写一个简单的二分答案

每次二分一个值,用queryrank(楼上)来查询这个值的rank

//无脑二分不多解释
int queryval(int ql,int qr,int val)//询问在[ql,qr]区间内排名为val的值
{
	int l=0,r=1e8,ans=-1;
	while(l<=r)
	{
		int mid=(l+r)>>1;	
		if(queryrank(1,1,n,ql,qr,mid)+1<=val)ans=mid,l=mid+1;
		else r=mid-1;
	}
	return ans;
}

3.修改k位置上的值

我们从根往[k,k]遍历,将路径上的节点中的平衡树中的k位置上的值先删除,然后再加入新的值,直到遍历到[k,k],跟往常的线段树修改没什么区别

void change(int k,int l,int r,int pos,int val)//将pos位置的值改为val
{
	a[k].del(a[k].rt,p[pos]);//删除pos位置的值
	a[k].ins(a[k].rt,val);//加入val
	if(l==r)return ;
	int mid=(l+r)>>1;
	if(pos<=mid)change(k<<1,l,mid,pos,val);
	else change(k<<1|1,mid+1,r,pos,val);
}

4. 查询k在区间内的前驱

查询前驱也没什么特别的,在询问区间范围内就调用平衡树,最后取左右子树查询的maxn

也可以像操作1那样分离

//不再注释
int querypre(int k,int l,int r,int ql,int qr,int val)//查询[ql,qr]区间内val的前驱
{
	if(l>=ql&&r<=qr)return a[k].pre(a[k].rt,val);
	int ans=-INF;
	int mid=(l+r)>>1;
	if(qr<=mid)ans=max(ans,querypre(k<<1,l,mid,ql,qr,val));
	else if(ql>mid)ans=max(ans,querypre(k<<1|1,mid+1,r,ql,qr,val));
	else ans=max(ans,max(querypre(k<<1,l,mid,ql,mid,val),querypre(k<<1|1,mid+1,r,mid+1,qr,val)));
	return ans;
}

5.查询k在区间内的后继

同上,后继取左右子树查询的minn

int querynxt(int k,int l,int r,int ql,int qr,int val)
{
	if(l>=ql&&r<=qr)return a[k].nxt(a[k].rt,val);
	int mid=(l+r)>>1;
	int ans=INF;
	if(qr<=mid)ans=min(ans,querynxt(k<<1,l,mid,ql,qr,val));
	else if(ql>mid)ans=min(ans,querynxt(k<<1|1,mid+1,r,ql,qr,val));
	else ans=min(ans,min(querynxt(k<<1,l,mid,ql,mid,val),querynxt(k<<1|1,mid+1,r,mid+1,qr,val)));
	return ans;
}

ps:树套树的时间复杂度都很优秀 ,所以需要注意一下卡常和各种玄学优化,(但这道题我加快读比不加慢?!)

代码:

#include<bits/stdc++.h>
using namespace std;
const int INF=0x7fffffff,N=5e4+10;
int n,m;
int p[N];
int cnt=0;
struct tree
{
	int ch[2],siz,val,rd;
}t[N*40];
int seed=233;	
struct FhqTreap
{
	int rt;
	int Rand()
	{
    	return seed=int(seed*4827111l%0x7ffffffff);
	}
	int newnode(int val)
	{
		t[++cnt].val=val;
		t[cnt].siz=1;
		t[cnt].rd=Rand();
		return cnt;
	}
	void up(int k)
	{
		t[k].siz=t[t[k].ch[0]].siz+t[t[k].ch[1]].siz+1;
	}
	void split(int now,int val,int &a,int &b)
	{
		if(!now)
		{
			a=b=0;
			return ;
		}
		if(t[now].val<=val)
		{
			a=now;
			split(t[now].ch[1],val,t[a].ch[1],b);
		}
		else
		{
			b=now;
			split(t[now].ch[0],val,a,t[b].ch[0]);
		}
		up(now);
	}
	int merge(int a,int b)
	{
		if(!(a&&b))return a+b;
		if(t[a].rd<t[b].rd)
		{
			t[a].ch[1]=merge(t[a].ch[1],b);
			up(a);
			return a;
		}
		else 
		{
			t[b].ch[0]=merge(a,t[b].ch[0]);
			up(b);
			return b;
		}
	}
	void ins(int &rt,int val)
	{
		int a,b;
		int c=newnode(val);
		split(rt,val,a,b);
		rt=merge(merge(a,c),b);
	}
	void del(int &rt,int val)
	{
		int a,b,c;
		split(rt,val,a,c);
		split(a,val-1,a,b);
		b=merge(t[b].ch[0],t[b].ch[1]);
		a=merge(a,b);
		rt=merge(a,c);
	}
	int rank(int rt,int val)
	{
		int a,b;
		split(rt,val-1,a,b);
		int ans=t[a].siz+1;
		rt=merge(a,b);
		return ans;
	}
	int kth(int now,int rk)
	{
		while((t[t[now].ch[0]].siz+1)!=rk)
		{
			if(t[t[now].ch[0]].siz>=rk)now=t[now].ch[0];
			else 
			{
				rk-=t[t[now].ch[0]].siz+1;
				now=t[now].ch[1];
			}
		}
		return t[now].val;
	}
	int pre(int rt,int val)
	{
		int a,b;
		split(rt,val-1,a,b);
		int ans=t[a].siz?kth(a,t[a].siz):-INF;
		rt=merge(a,b);
		return ans;
	}
	int nxt(int rt,int val)
	{
		int a,b;
		split(rt,val,a,b);
		int ans=t[b].siz>0?kth(b,1):INF;
		rt=merge(a,b);
		return ans;
	}
}a[N<<2];
void build(int k,int l,int r)
{
	for(int i=l;i<=r;i++)a[k].ins(a[k].rt,p[i]);
	if(l==r)return ;
	int mid=(l+r)>>1;
	build(k<<1,l,mid);
	build(k<<1|1,mid+1,r);
}
int queryrank(int k,int l,int r,int ql,int qr,int val)
{
	if(ql<=l&&r<=qr)return a[k].rank(a[k].rt,val)-1;
	int ans=0;
	int mid=(l+r)>>1;
	if(qr<=mid)ans=queryrank(k<<1,l,mid,ql,qr,val);
	else if(ql>mid)ans=queryrank(k<<1|1,mid+1,r,ql,qr,val);
	else ans=queryrank(k<<1,l,mid,ql,mid,val)+queryrank(k<<1|1,mid+1,r,mid+1,qr,val);
	return ans;
}
int queryval(int ql,int qr,int val)
{
	int l=0,r=1e8,ans=-1;
	while(l<=r)
	{
		int mid=(l+r)>>1;	
		if(queryrank(1,1,n,ql,qr,mid)+1<=val)ans=mid,l=mid+1;
		else r=mid-1;
	}
	return ans;
}
void change(int k,int l,int r,int pos,int val)
{
	a[k].del(a[k].rt,p[pos]);
	a[k].ins(a[k].rt,val);
	if(l==r)return ;
	int mid=(l+r)>>1;
	if(pos<=mid)change(k<<1,l,mid,pos,val);
	else change(k<<1|1,mid+1,r,pos,val);
}
int querypre(int k,int l,int r,int ql,int qr,int val)
{
	if(l>=ql&&r<=qr)return a[k].pre(a[k].rt,val);
	int ans=-INF;
	int mid=(l+r)>>1;
	if(qr<=mid)ans=max(ans,querypre(k<<1,l,mid,ql,qr,val));
	else if(ql>mid)ans=max(ans,querypre(k<<1|1,mid+1,r,ql,qr,val));
	else ans=max(ans,max(querypre(k<<1,l,mid,ql,mid,val),querypre(k<<1|1,mid+1,r,mid+1,qr,val)));
	return ans;
}
int querynxt(int k,int l,int r,int ql,int qr,int val)
{
	if(l>=ql&&r<=qr)return a[k].nxt(a[k].rt,val);
	int mid=(l+r)>>1,ans=INF;
	if(qr<=mid)ans=min(ans,querynxt(k<<1,l,mid,ql,qr,val));
	else if(ql>mid)ans=min(ans,querynxt(k<<1|1,mid+1,r,ql,qr,val));
	else ans=min(ans,min(querynxt(k<<1,l,mid,ql,mid,val),querynxt(k<<1|1,mid+1,r,mid+1,qr,val)));
	return ans;
}
inline int read()
{
	int x=0,f=1;
	char ch=getchar();
	while(!isdigit(ch))
	{
		if(ch=='-')f=-1;
		ch=getchar();
	}
	while(isdigit(ch))
	{
		x=(x<<3)+(x<<1)+(ch^48);
		ch=getchar();
	}
	return x*f;
}
int main()
{
	srand(19260817);
	n=read(),m=read();
	for(int i=1;i<=n;i++)p[i]=read();
	build(1,1,n);
	int op,l,r,k,pos;
	for(int i=1;i<=m;i++)
	{
		op=read();
		if(op==1)
		{
			l=read(),r=read(),k=read();
			printf("%d\n",queryrank(1,1,n,l,r,k)+1);
		}
		if(op==2)
		{
			l=read(),r=read(),k=read();
			printf("%d\n",queryval(l,r,k));
		}
		if(op==3)
		{
			pos=read(),k=read();
			change(1,1,n,pos,k);
			p[pos]=k;
		}
		if(op==4)
		{
			l=read(),r=read(),k=read();
			printf("%d\n",querypre(1,1,n,l,r,k));
		}
		if(op==5)
		{
			l=read(),r=read(),k=read();
			printf("%d\n",querynxt(1,1,n,l,r,k));
		}
	}
	return 0;
}
/*
9 6
4 2 2 1 9 4 0 1 1
2 1 4 3
3 4 10
2 1 4 3
1 2 5 9
4 3 9 5
5 2 8 5
*/

posted @   ShuraEye  阅读(857)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示