洛谷P3369 【模板】普通平衡树 [权值线段树做法]

D e s c r i p t i o n Description Description

写一种数据结构维护一些数据
包括插入,查找,删除,查找前驱,查找后继等

数据范围:数的个数 n ≤ 1 0 6 n\leq 10^6 n106


S o l u t i o n Solution Solution

其实我们只需要写出一种数据结构满足查询排名为 p p p的数, p p p这个数的排名,以及支持修改的数据结构即可

那前驱和后继咋办呢?
p r e ( x ) = k t h ( r k ( x ) − 1 ) , n x t ( x ) = k t h ( r k ( x + 1 ) ) pre(x)=kth(rk(x)-1),nxt(x)=kth(rk(x+1)) pre(x)=kth(rk(x)1),nxt(x)=kth(rk(x+1))
关于上述结论证明方法很多,这里不再阐述。

权值线段树或者树状数组套二分就可以解决这个问题,由于本题是单点修改,所以相对而言树状数组会好写很多,但用下标建一样,线段树普遍比树状数组好拓展很多,所以这里主要介绍的是权值线段树的做法

思想都很简单,就是在值域上建线段树(树状数组),线段树利用分治的思想,树状数组利用二进制差分前缀和的思想,我们就可以统计对于一个数值 x x x,有多少个数比它小(树状数组直接查询,权值线段树递归下去),同理可以得到这个数的排名

至于查询排名为 p p p的数

权值线段树维护一个 s u m sum sum表示对于区间的区间和, s u m [ 1 ] sum[1] sum[1]表示 [ m i n l . ∼ m a x r ] [minl.\sim maxr] [minl.maxr]的和, s u m [ 2 ] sum[2] sum[2]表示 [ m i n l ∼ ( m i n l + m a x r ) ] [minl\sim(minl+maxr)] [minl(minl+maxr)] s u m [ 3 ] sum[3] sum[3]表示 [ ( m i n l + m a x r ) / 2 + 1 ∼ m a x r ] [(minl+maxr)/2+1\sim maxr] [(minl+maxr)/2+1maxr]的和,以此类推

如果左区间的数的个数大于 p p p,即 s u m [ l s o n ] > = p sum[lson]>=p sum[lson]>=p,说明这个数一定在左区间,则往左区间走

否则,说明排名为 p p p的数一定在右区间,此时就相当于在右子树查询排名为 p − s u m [ l s o n ] p-sum[lson] psum[lson]的数的排名

备注:上面这句话很重要!!!最好理解完了再看代码!

到这问题就基本解决,每个数的范围 ∣ a i ∣ ≤ 1 0 7 |a_i|\leq 10^7 ai107,所以酱紫做的复杂度为 O ( m l o g ( 2 × 1 0 7 ) ) ≈ O ( 2.43 × 1 0 7 ) O(mlog(2\times 10^7))\approx O(2.43\times 10^7) O(mlog(2×107))O(2.43×107)足够通过本题

离线(离散化)之后可以跑得更快,时间复杂度就变成了 O ( m l o g m ) ≈ O ( 2.0 × 1 0 7 ) O(mlogm)\approx O(2.0\times 10^7) O(mlogm)O(2.0×107)

本代码是离散化后的代码


C o d e Code Code

#include 
#include
#include
#define N 400010
#define maxn 100000
#define lson k<<1
#define rson k<<1|1
using namespace std;int sum[N],n,m,k,a[maxn+10],b[maxn+10],op[maxn+10],tot;
long long ans;
inline long long read()
{
    char c;int d=1;long long f=0;
    while(c=getchar(),!isdigit(c))if(c==45)d=-1;f=(f<<3)+(f<<1)+c-48;
    while(c=getchar(),isdigit(c)) f=(f<<3)+(f<<1)+c-48;
    return d*f;
}
inline void add(int x,int d,int l=1,int r=tot,int k=1)//单点修改
{
	sum[k]+=d;
	if(l==r) return;
	int mid=l+r>>1;
	if(x<=mid) add(x,d,l,mid,lson);//在左边
	else add(x,d,mid+1,r,rson);//在右边
	return;
}
inline int kth(int p,int l=1,int r=tot,int k=1)//查询排名为k的数
{
	if(l==r) return l;
	int s=sum[lson],mid=l+r>>1;
	if(s>=p) return kth(p,l,mid,lson);//在左子树
	return kth(p-s,mid+1,r,rson);//在右子树
}
inline int rank(int p,int l=1,int r=tot,int k=1)
{
	if(l==r) return 1;
	int s=sum[lson],mid=l+r>>1;
	if(p<=mid) return rank(p,l,mid,lson);//在左子树
	return s+rank(p,mid+1,r,rson);//在右子树,注意此时它的排名要加上sum[lson],因为这些数都比它小!!!
}
signed main()
{
	m=read();
	for(register int i=1;i<=m;i++)
	{
		op[i]=read();a[i]=read();
		if(op[i]!=4) b[++tot]=a[i];
	}
	sort(b+1,b+1+tot);
	tot=unique(b+1,b+1+tot)-b-1;
	for(register int i=1;i<=m;i++) if(op[i]!=4) a[i]=lower_bound(b+1,b+1+tot,a[i])-b;//离散化
	for(register int i=1;i<=m;i++)
	{
		if(op[i]==1) add(a[i],1);
		if(op[i]==2) add(a[i],-1);
		if(op[i]==3) printf("%d\n",rank(a[i]));
		if(op[i]==4) printf("%d\n",b[kth(a[i])]);
		if(op[i]==5) printf("%d\n",b[kth(rank(a[i])-1)]);
		if(op[i]==6) printf("%d\n",b[kth(rank(a[i]+1))]);
	}
}

你可能感兴趣的:(线段树,权值线段树)