Title

权值线段树

定义

记录权值的线段树。因此需要用到离散化操作来处理a[1-n]。记录权值指的是,每个点上存的是区间内的数字出现的总次数。

作用

  • 查询第k小或第k大。
  • 查询某个数排名。
  • 查询数组的排序。
  • 查询前驱和后继(比某个数小的最大值,比某个数大的最小值)
  • 求逆序对
优劣
  • 相对平衡树:常数小,代码简单
  • 若值过大,需要离散化,则变为离线数据结构

操作实现代码(讲解都在代码注释里):

#include<bits/stdc++.h>
using namespace std;
#define N 1000010
int tree[N],num[N],b[N],a[N]; 

#define lson pos<<1//将lson的值赋为pos*2 
#define rson pos<<1|1//将rson的值赋为pos*2+1,用一维数组存储树,线段树 

inline void build(int pos,int l,int r){//建树,tree[pos]表示从l到r的区间内数出现的次数,tree[lson]存储从l到中点的区间内数出现的个数,tree[rson]存储从mid+1到r的区间内数出现的次数 
	int mid=(l+r)>>1;//把mid的值取为区间中间值 
	if(l==r){//如果l==r,说明找到了叶子节点,即单个数,此时tree[pos]存储单个点出现的次数 
		tree[pos]+=a[l];//a[l]表示数l出现的次数 
		return;
	}
	//每一次,随着pos值的更新,lson和rson的值也会随之更新 
	build(lson,l,mid);//构建左子树,区间范围为从当前的区间的左节点到区间的中点 
	build(rson,mid+1,r);//构建右子树,区间范围为从当前的区间的中点右边的第一个节点到当前区间的右节点 
	tree[pos]=tree[lson]+tree[rson];//如果该节点不是叶子节点,将它的值更新为做子树的的值和右子树的值的和 
} 

inline void update(int pos,int l,int r,int k,int cnt){//插入cnt个k,cnt、k表示数k的个数多cnt个
	int mid=(l+r)>>1;//mid为区间的中点(向下取整) 
	if(l==r){//如果找到了叶子节点,也就是说找到了k 
		tree[pos]+=cnt;//把当前节点的值(当前数出现的次数)+ 新增加的次数 
		return;
	}
	if(k<=mid)//如果k在区间的左半边里 
		update(lson,l,mid,k,cnt);//搜索左子树 
	else	//如果k在区间的右半边里(不包括中点) 
		update(rson,mid+1,r,k,cnt);//搜索右子树 
	tree[pos]=tree[lson]+tree[rson];//更新每一个节点 
}

inline int query(int pos,int l,int r,int k){//查询值为k的数有多少个 
	int mid=(l+r)>>1;//mid为区间中点 
	if(l==r)//找到了 
		return tree[pos];
	if(k<=mid)//k在中点左侧 
		return query(lson,l,mid,k);//搜索左子树 
	else//k在中点右侧 
		return query(rson,mid+1,r,k);//搜索右子树 
}

inline int kth(int pos,int l,int r,int k){//查询k小值 
	if(l==r) return l;//找到了,返回下标 
	int mid=(l+r)>>1;//mid为中点 
	if(tree[pos<<1]>=k) return kth(pos<<1,l,mid,k);//如果当前点的左节点比k大,说明k在左子树中,搜索左子树 
	else return kth(pos<<1|1,mid+1,r,k-tree[pos<<1]); //否则的话,k在右子树中,更新k的值,把右节点更新为根节点,搜索右子树 
}

inline int findl(int rt,int l,int r){
    if(l==r) return l;
    int m=(l+r)>>1;
    if(tree[rt<<1|1]) return findl(rt<<1|1,m+1,r);
    return findl(rt<<1,l,m);
}

inline int pre(int p,int rt,int l,int r){//查找前驱 
    if(r<p){
        if(tree[rt]) return findl(rt,l,r);//查询最靠右的数
        return 0;
    }
    int m=(l+r)>>1,re;
    if(m<p-1&&tree[rt<<1|1]&&(re=pre(p,rt<<1|1,m+1,r))) return re; //先考虑右子树
    return pre(p,rt<<1,l,m);
}

inline int nex(int p,int rt,int l,int r){//查找后继 
    if(p<l){
        if(tree[rt]) return findl(rt,l,r);
        return 0;
    }
    int m=(l+r)>>1;
    int re;
    if(p<m&&tree[rt<<1]&&(re=nex(p,rt<<1,l,m))) return re;
    return nex(p,rt<<1|1,m+1,r);
}

int q,s,n;
inline void init(){
	cin>>n;//输入n,区间内共n个数 
	for(int i=1;i<=n;i++) {cin>>num[i];b[i]=num[i];} //依次输入区间内每个数的值 
	sort(b+1,b+n+1);//把b数组排序 
	s=unique(b+1,b+n+1)-(b+1);//b数组排序后进行去重操作 
	for(int i=1;i<=n;i++){int v=lower_bound(b+1,b+s+1,num[i])-b;a[v]++;} //把a数组离散化,并获取每个元素,初始化a数组
	build(1,1,s); //建树
}

int opt,x;
inline void work(){
	while(q--){
		cin>>opt>>x;
		if(opt==1) update(1,1,s,x,1); //插入一个数 
		if(opt==2) update(1,1,s,x,-1); //删除一个数 
		if(opt==3) {int v=lower_bound(b+1,b+s+1,x)-b;cout<<query(1,1,s,v)<<endl;} //查询x有几个 
		if(opt==4) cout<<b[kth(1,1,s,x)]<<endl; //查询第x小的数 
		if(opt==5) cout<<b[kth(1,1,s,s-x+1)]<<endl; //查询第x大的数 
		if(opt==6) {int v=lower_bound(b+1,b+s+1,x)-b;cout<<pre(v,1,1,s)<<endl;} //查找x的前驱 
		if(opt==7) {int v=lower_bound(b+1,b+s+1,x)-b;cout<<nex(v,1,1,s)<<endl;} //查找x的后继 
	} 
}

int main(){
	init();
	cin>>q;
	work();
	return 0;
}

制作不易,求赞

posted @   UncleSam_Died  阅读(6)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 一文读懂知识蒸馏
· 终于写完轮子一部分:tcp代理 了,记录一下
点击右上角即可分享
微信分享提示