权值线段树

前言

因为不会其它题所以来投奔数据结构了

1. 原理 & 例题

权值线段树(广泛简称为权值树)是一种维护每种数字出现的次数的数据结构,类似于一个桶,但是用一颗线段树封装来进行更快的修改操作。
可以过平衡树的板子题 P3369 【模板】普通平衡树
但是权值线段树只能支持单点的修改和全局的查询操作。
尽管如此,权值线段树有许多奇妙的性质,用这些性质可以完成许多高级操作。
权值线段树也可以动态开点。
为了熟悉这种线段树,我们一点一点实现例题中的操作。

  1. 插入一个数 \(x\)
  2. 删除一个数 \(x\)(若有多个相同的数,应只删除一个)。
  3. 定义排名为比当前数小的数的个数 \(+1\)。查询 \(x\) 的排名。
  4. 查询数据结构中排名为 \(x\) 的数。
  5. \(x\) 的前驱(前驱定义为小于 \(x\),且最大的数)。
  6. \(x\) 的后继(后继定义为大于 \(x\),且最小的数)。

因为是数据结构,先来看看 struct 怎么写,这里先用普通的权值树做例子。

struct seg{
	int l;
	int r;
	int cnt;
	int no0;
};
seg segtree[(int)1e6+5];

在这里,int l int r 表示值域区间的左端点和右端点,int cnt 表示在值域 \([l,r]\) 范围中出现的数字个数int no0 表示在值域 \([l,r]\) 范围中出现的数字种类的个数

建树和普通的线段树没什么不同,就不放代码了(下面的全代码展示里有)。

单点加操作

单点修改也没什么好说的,注意要同时更新 cnt no0 的值。

void add(int p,int k){
	if(segtree[p].l==k&&segtree[p].r==k){
	//左端点和右端点相等且都等与 k 那就代表找到这个节点的直接更新 
		segtree[p].cnt++;
		if(segtree[p].cnt>0){//更新 no0 变量 
			segtree[p].no0=1;
		}
		return;//注意 return 不然 RE 
	}
	int mid=segtree[p].l+segtree[p].r>>1;
	if(k<=mid){//在中心左边,去左边查找 
		add(p*2,k);
	}
	else if(k>mid){
		add(p*2+1,k);//去右边 
	}
	segtree[p].cnt=segtree[p*2].cnt+segtree[p*2+1].cnt;//更新两个变量 
	segtree[p].no0=segtree[p*2].no0+segtree[p*2+1].no0;
	return;
}

单点删除操作

同单点加操作,没什么好说的。

void del(int p,int k){
	if(segtree[p].l==k&&segtree[p].r==k){
		segtree[p].cnt--;
		if(segtree[p].cnt==0){
			segtree[p].no0=0;
		}
		return;
	}
	int mid=segtree[p].l+segtree[p].r>>1;
	if(k<=mid){
		del(p*2,k);
	}
	else if(k>mid){
		del(p*2+1,k);
	}
	segtree[p].cnt=segtree[p*2].cnt+segtree[p*2+1].cnt;
	segtree[p].no0=segtree[p*2].no0+segtree[p*2+1].no0;
	return;
}

查询某个数的排名

其实是查找在序列中出现了多少个比自己小的数,在权值树中相当于前缀查询,要注意把自己排除掉,注释很详细:

int ask_rank(int p,int k){
	if(segtree[p].l==k&&segtree[p].r==k){
	//虽然找到了自己,但要把自己排除 
		return 0;
	}
	if(segtree[p].r<k){
	//比自己值小的区间直接返回区间内出现的数的数量作为答案 
		return segtree[p].cnt;
	}
	int mid=segtree[p].l+segtree[p].r>>1;
	if(mid<k){//如果左右区间都有包括 
		int r1=ask_rank(p*2,k);//找左边(其实可以直接赋值) 
		int r2=ask_rank(p*2+1,k);//找右边
		return r1+r2;//返回和值 
	}
	if(k<=mid){//只包括了左区间
        //找左边(其实可以直接返回 segtree[p*2].cnt 统一了好看一点)
		return ask_rank(p*2,k); 
	}
}

查询排名为某个值的数

设要查询的排名为 \(k\)。很显然有这样的性质:如果一棵树的左子树拥有的总排名(也就是 segtree[p*2].cnt大于 \(k\),那么那一个排名为 \(k\) 的数一定在左子树的值域范围内,问题变成在左子树内求排名为 \(k\) 的数。
同理,如果一棵树的左子树拥有的总排名小于 \(k\),那么那一个排名为 \(k\) 的数一定在子树的值域范围内,问题变成在右子树内求排名为 \(k-segtree[p\times 2].cnt\) 的数。
综合上述思路可以写出代码,注释很详细。

int ask_element(int p,int k){
	if(segtree[p].l==segtree[p].r){
	//左端点等于右端点代表找到了,返回这个端点的值 
		return segtree[p].l;
	}
	if(k-segtree[p*2].cnt<=0){
	//一棵树的左子树拥有的总排名大于 k,问题变成在左子树内求排名为 k 的数。   
		return ask_element(p*2,k);
	}
	else{
	//一棵树的左子树拥有的总排名小于 k,问题变成在右子树内求排名为 k-segtree[p*2].cnt 的数。
		return ask_element(p*2+1,k-segtree[p*2].cnt);
	}
	return -1;//没找到返回 -1,事实上并没有返回 -1 的时候。 
}

查询前驱

设要查询前驱的值为 \(k\)
也就是找出现的最大的且严格比 \(k\) 小的数,由于要求最大,我们要在值域区间不大于 \(k\) 的情况下尽可能往右找。
分情况简单讨论一下我们怎么办:

  • 如果 \(k\) 在当前右子树的值域范围内,且右子树值域范围内出现的数的个数不为 \(0\),才向右子树处递归寻找(即使第一个出现的数就大于 \(k\))。如果没有找到,再向左子树处递归寻找。
  • 如果 \(k\) 在当前右子树的值域范围内,且右子树值域范围内出现的数的个数 \(0\),直接向左子树处递归寻找。
  • 如果 \(k\) 在当前左子树的值域范围内,且左子树值域范围内出现的数的个数不为 \(0\),直接向左子树处递归寻找。
  • 如果以上都不满足,返回 \(-1\) 代表没找到(虽然测试数据中好像没出现最终返回值是找不到的情况,但这里是为了在递归中决策)。

(当然作者这样的做法非常麻烦,其实你可以结合 "查询值的排名"、"查询排名为某个值的数" 这两个操作来实现这个操作,翻了翻题解区好像只有作者傻到这么干了,毕竟作者没有这个脑子)。

注释依然很详细:

int ask_pre(int p,int k){
	if(segtree[p].l==segtree[p].r&&segtree[p].no0&&segtree[p].l!=k){
		//找到了 且自己出现过 且不是 k 自己,直接返回 
		return segtree[p].l;
	}
	else if(segtree[p].l==segtree[p].r){
		return -1;//不满足后两个条件依然认为没找到 
	}
	int mid=segtree[p].l+segtree[p].r>>1;
	if(mid<k&&segtree[p*2+1].no0!=0){
	//如果 k 在当前右子树的值域范围内,且右子树值域范围内出现的数的个数不为 0,才向右子树处递归寻找。 
		int ret=ask_pre(p*2+1,k);
		if(ret!=-1)return ret;//找到了直接返回 
		else return ask_pre(p*2,k);//没找到才向左找(直接 return 就可以没必要继续因为两个区间都已经查完了) 
	}
	if(mid<k&&segtree[p*2+1].no0==0&&segtree[p*2].no0!=0){
	//如果 k 在当前右子树的值域范围内,且右子树值域范围内出现的数的个数为 0,直接向左子树处递归寻找。 
		int ret=ask_pre(p*2,k);
		return ret;//直接 return 因为没的选了 
	}
	if(k<=mid&&segtree[p*2].no0!=0){
	//如果 k 在当前左子树的值域范围内,且左子树值域范围内出现的数的个数不为 0,直接向左子树处递归寻找 
		int ret=ask_pre(p*2,k);
		return ret;//直接 return 因为没的选了
	}
	return -1;//都不满足报告没找到 
}

查询后继

思路和找前驱差不多,只是要在不小于的情况下尽量向左找,也可以结合 "查询值的排名"、"查询排名为某个值的数" 这两个操作来实现这个操作。这里不在重复叙述思路了。

int ask_suc(int p,int k){
	if(segtree[p].l==segtree[p].r&&segtree[p].no0&&segtree[p].l!=k){
		//找到了 且自己出现过 且不是 k 自己,直接返回
		return segtree[p].l;
	}
	else if(segtree[p].l==segtree[p].r){
		return -1;//不满足后两个条件依然认为没找到
	}
	int mid=segtree[p].l+segtree[p].r>>1;
	if(k<=mid&&segtree[p*2].no0!=0){
	//如果 k 在当前左子树的值域范围内,且左子树值域范围内出现的数的个数不为 0,才向左子树处递归寻找。
		int ret=ask_suc(p*2,k);//不要因为复制粘贴没改递归调用的名字!!!!! 
		if(ret!=-1)return ret;//找到了直接返回
		else return ask_suc(p*2+1,k);//没找到才向右找
	}
	if(k<=mid&&segtree[p*2].no0==0&&segtree[p*2+1].no0!=0){
	//如果 k 在当前左子树的值域范围内,且左子树值域范围内出现的数的个数为 0,直接向右子树处递归寻找。 	
		int ret=ask_suc(p*2+1,k);
		return ret;
	}
	if(mid<k&&segtree[p*2+1].no0!=0){
	//如果 k 在当前右子树的值域范围内,且右子树值域范围内出现的数的个数不为 0,直接向右子树处递归寻找
		int ret=ask_suc(p*2+1,k);
		return ret;
	}
	return -1;//都不满足报告没找到
}

AC 前的鲜花

马上就可以切了这道题了!但是要注意这题的值域大小是 \(2\times 10^7\),因此要对所有输入数据离散化后在处理。
如果不想离散化,可以使用 动态开点权值线段树,这个会在下文介绍。
离散化使用标准的排序 \(+\) 去重 \(+\) 二分。

代码时间(可通过点击右侧导航栏跳转)

不带注释版

#define rd read()
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll read(){
	ll x=0,f=1;
	char c=getchar();
	while(c>'9'||c<'0'){if(c=='-') f=-1;c=getchar();}
	while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
const int N=3e5+5;
struct oper{
	int type;
	int var;
}o[N];
int lisan[N];
int cnt=0;
struct seg{
	int l;
	int r;
	int cnt;
	int no0;
};
seg segtree[(int)1e6+5];
void buildt(int p,int l,int r){
	segtree[p].l=l;
	segtree[p].r=r;
	if(l==r){
		segtree[p].cnt=0;
		return;
	}
	int mid=l+r>>1;
	buildt(p*2,l,mid);
	buildt(p*2+1,mid+1,r);
	segtree[p].cnt=segtree[p*2].cnt+segtree[p*2+1].cnt;
	segtree[p].no0=segtree[p*2].no0+segtree[p*2+1].no0;
	return;
}
void add(int p,int k){
	if(segtree[p].l==k&&segtree[p].r==k){
		segtree[p].cnt++;
		if(segtree[p].cnt>0){
			segtree[p].no0=1;
		}
		return;
	}
	int mid=segtree[p].l+segtree[p].r>>1;
	if(k<=mid){
		add(p*2,k);
	}
	else if(k>mid){
		add(p*2+1,k);
	}
	segtree[p].cnt=segtree[p*2].cnt+segtree[p*2+1].cnt;
	segtree[p].no0=segtree[p*2].no0+segtree[p*2+1].no0;
	return;
}
void del(int p,int k){
	if(segtree[p].l==k&&segtree[p].r==k){
		segtree[p].cnt--;
		if(segtree[p].cnt==0){
			segtree[p].no0=0;
		}
		return;
	}
	int mid=segtree[p].l+segtree[p].r>>1;
	if(k<=mid){
		del(p*2,k);
	}
	else if(k>mid){
		del(p*2+1,k);
	}
	segtree[p].cnt=segtree[p*2].cnt+segtree[p*2+1].cnt;
	segtree[p].no0=segtree[p*2].no0+segtree[p*2+1].no0;
	return;
}
int ask_rank(int p,int k){
	if(segtree[p].l==k&&segtree[p].r==k){
		return 0;
	}
	if(segtree[p].r<k){
		return segtree[p].cnt;
	}
	int mid=segtree[p].l+segtree[p].r>>1;
	if(mid<k){
		int r1=ask_rank(p*2,k);
		int r2=ask_rank(p*2+1,k);
		return r1+r2;
	}
	if(k<=mid){
		return ask_rank(p*2,k);
	}
}
int ask_element(int p,int k){
	if(segtree[p].l==segtree[p].r){
		return segtree[p].l;
	}
	if(k-segtree[p*2].cnt<=0){
		return ask_element(p*2,k);
	}
	else{
		return ask_element(p*2+1,k-segtree[p*2].cnt);
	}
	return -1;
}
int ask_pre(int p,int k){
	if(segtree[p].l==segtree[p].r&&segtree[p].no0&&segtree[p].l!=k){
		return segtree[p].l;
	}
	else if(segtree[p].l==segtree[p].r){
		return -1;
	}
	int mid=segtree[p].l+segtree[p].r>>1;
	if(mid<k&&segtree[p*2+1].no0!=0){
		int ret=ask_pre(p*2+1,k);
		if(ret!=-1)return ret;
		else return ask_pre(p*2,k);
	}
	if(mid<k&&segtree[p*2+1].no0==0&&segtree[p*2].no0!=0){
		int ret=ask_pre(p*2,k);
		return ret;
	}
	if(k<=mid&&segtree[p*2].no0!=0){
		int ret=ask_pre(p*2,k);
		return ret;
	}
	if(segtree[p*2].no0==0){
		return -1;
	}
	return -1;
}
int ask_suc(int p,int k){
	if(segtree[p].l==segtree[p].r&&segtree[p].no0&&segtree[p].l!=k){
		return segtree[p].l;
	}
	else if(segtree[p].l==segtree[p].r){
		return -1;
	}
	int mid=segtree[p].l+segtree[p].r>>1;
	if(k<=mid&&segtree[p*2].no0!=0){
		int ret=ask_suc(p*2,k);
		if(ret!=-1)return ret;
		else return ask_suc(p*2+1,k);
	}
	if(k<=mid&&segtree[p*2].no0==0&&segtree[p*2+1].no0!=0){
		int ret=ask_suc(p*2+1,k);
		return ret;
	}
	if(mid<k&&segtree[p*2+1].no0!=0){
		int ret=ask_suc(p*2+1,k);
		return ret;
	}
	if(segtree[p*2+1].no0==0){
		return -1;
	}
	return -1;
}
int main(){

	int n;
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>o[i].type>>o[i].var;
		if(o[i].type!=4){
			lisan[++cnt]=o[i].var;
		}
	}
	sort(lisan+1,lisan+1+cnt);
	int len=unique(lisan+1,lisan+1+cnt)-lisan;
	buildt(1,1,len+5);
	for(int i=1;i<=n;i++){
		int op=o[i].type,var=o[i].var;
		int wp=lower_bound(lisan+1,lisan+1+len,var)-lisan;
		if(op==1){
			add(1,wp);
		}
		else if(op==2){
			del(1,wp);
		}
		else if(op==3){
			cout<<ask_rank(1,wp)+1<<'\n';
		}
		else if(op==4){
			cout<<lisan[ask_element(1,var)]<<'\n';
		}
		else if(op==5){
			cout<<lisan[ask_pre(1,wp)]<<'\n';
		}
		else if(op==6){
			cout<<lisan[ask_suc(1,wp)]<<'\n';
		}
	}

	return 0;
}

全注释存档

#define rd read()
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll read(){
	ll x=0,f=1;
	char c=getchar();
	while(c>'9'||c<'0'){if(c=='-') f=-1;c=getchar();}
	while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
const int N=3e5+5;
struct oper{
	int type;
	int var;
}o[N];
int lisan[N];
int cnt=0;
struct seg{
	int l;
	int r;
	int cnt;
	int no0;
};
seg segtree[(int)1e6+5];
void buildt(int p,int l,int r){
	segtree[p].l=l;
	segtree[p].r=r;
	if(l==r){
		segtree[p].cnt=0;
		return;
	}
	int mid=l+r>>1;
	buildt(p*2,l,mid);
	buildt(p*2+1,mid+1,r);
	segtree[p].cnt=segtree[p*2].cnt+segtree[p*2+1].cnt;
	segtree[p].no0=segtree[p*2].no0+segtree[p*2+1].no0;
	return;
}
void add(int p,int k){
	if(segtree[p].l==k&&segtree[p].r==k){
	//左端点和右端点相等且都等与 k 那就代表找到这个节点的直接更新 
		segtree[p].cnt++;
		if(segtree[p].cnt>0){//更新 no0 变量 
			segtree[p].no0=1;
		}
		return;//注意 return 不然 RE 
	}
	int mid=segtree[p].l+segtree[p].r>>1;
	if(k<=mid){//在中心左边,去左边查找 
		add(p*2,k);
	}
	else if(k>mid){
		add(p*2+1,k);//去右边 
	}
	segtree[p].cnt=segtree[p*2].cnt+segtree[p*2+1].cnt;//更新两个变量 
	segtree[p].no0=segtree[p*2].no0+segtree[p*2+1].no0;
	return;
}
void del(int p,int k){
	if(segtree[p].l==k&&segtree[p].r==k){
		segtree[p].cnt--;
		if(segtree[p].cnt==0){
			segtree[p].no0=0;
		}
		return;
	}
	int mid=segtree[p].l+segtree[p].r>>1;
	if(k<=mid){
		del(p*2,k);
	}
	else if(k>mid){
		del(p*2+1,k);
	}
	segtree[p].cnt=segtree[p*2].cnt+segtree[p*2+1].cnt;
	segtree[p].no0=segtree[p*2].no0+segtree[p*2+1].no0;
	return;
}
int ask_rank(int p,int k){
	if(segtree[p].l==k&&segtree[p].r==k){
	//虽然找到了自己,但要把自己排除 
		return 0;
	}
	if(segtree[p].r<k){
	//比自己值小的区间直接返回区间内出现的数的数量作为答案 
		return segtree[p].cnt;
	}
	int mid=segtree[p].l+segtree[p].r>>1;
	if(mid<k){//如果左右区间都有包括 
		int r1=ask_rank(p*2,k);//找左边(其实可以直接赋值) 
		int r2=ask_rank(p*2+1,k);//找右边
		return r1+r2;//返回和值 
	}
	if(k<=mid){//只包括了左区间
		return ask_rank(p*2,k);//找左边(其实可以直接返回 segtree[p*2].cnt 统一了好看一点) 
	}
}
int ask_element(int p,int k){
	if(segtree[p].l==segtree[p].r){
	//左端点等于右端点代表找到了,返回这个端点的值 
		return segtree[p].l;
	}
	if(k-segtree[p*2].cnt<=0){
	//一棵树的左子树拥有的总排名大于 k,问题变成在左子树内求排名为 k 的数。   
		return ask_element(p*2,k);
	}
	else{
	//一棵树的左子树拥有的总排名小于 k,问题变成在右子树内求排名为 k-segtree[p*2].cnt 的数。
		return ask_element(p*2+1,k-segtree[p*2].cnt);
	}
	return -1;//没找到返回 -1,事实上并没有返回 -1 的时候。 
}
int ask_pre(int p,int k){
	if(segtree[p].l==segtree[p].r&&segtree[p].no0&&segtree[p].l!=k){
		//找到了 且自己出现过 且不是 k 自己,直接返回 
		return segtree[p].l;
	}
	else if(segtree[p].l==segtree[p].r){
		return -1;//不满足后两个条件依然认为没找到 
	}
	int mid=segtree[p].l+segtree[p].r>>1;
	if(mid<k&&segtree[p*2+1].no0!=0){
	//如果 k 在当前右子树的值域范围内,且右子树值域范围内出现的数的个数不为 0,才向右子树处递归寻找。 
		int ret=ask_pre(p*2+1,k);
		if(ret!=-1)return ret;//找到了直接返回 
		else return ask_pre(p*2,k);//没找到才向左找(直接 return 就可以没必要继续因为两个区间都已经查完了) 
	}
	if(mid<k&&segtree[p*2+1].no0==0&&segtree[p*2].no0!=0){
	//如果 k 在当前右子树的值域范围内,且右子树值域范围内出现的数的个数为 0,直接向左子树处递归寻找。 
		int ret=ask_pre(p*2,k);
		return ret;//直接 return 因为没的选了 
	}
	if(k<=mid&&segtree[p*2].no0!=0){
	//如果 k 在当前左子树的值域范围内,且左子树值域范围内出现的数的个数不为 0,直接向左子树处递归寻找 
		int ret=ask_pre(p*2,k);
		return ret;//直接 return 因为没的选了
	}
	return -1;//都不满足报告没找到 
}
int ask_suc(int p,int k){
	if(segtree[p].l==segtree[p].r&&segtree[p].no0&&segtree[p].l!=k){
		//找到了 且自己出现过 且不是 k 自己,直接返回
		return segtree[p].l;
	}
	else if(segtree[p].l==segtree[p].r){
		return -1;//不满足后两个条件依然认为没找到
	}
	int mid=segtree[p].l+segtree[p].r>>1;
	if(k<=mid&&segtree[p*2].no0!=0){
	//如果 k 在当前左子树的值域范围内,且左子树值域范围内出现的数的个数不为 0,才向左子树处递归寻找。
		int ret=ask_suc(p*2,k);//不要因为复制粘贴没改递归调用的名字!!!!! 
		if(ret!=-1)return ret;//找到了直接返回
		else return ask_suc(p*2+1,k);//没找到才向右找
	}
	if(k<=mid&&segtree[p*2].no0==0&&segtree[p*2+1].no0!=0){
	//如果 k 在当前左子树的值域范围内,且左子树值域范围内出现的数的个数为 0,直接向右子树处递归寻找。 	
		int ret=ask_suc(p*2+1,k);
		return ret;
	}
	if(mid<k&&segtree[p*2+1].no0!=0){
	//如果 k 在当前右子树的值域范围内,且右子树值域范围内出现的数的个数不为 0,直接向右子树处递归寻找
		int ret=ask_suc(p*2+1,k);
		return ret;
	}
	return -1;//都不满足报告没找到
}
int main(){

	int n;
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>o[i].type>>o[i].var;
		if(o[i].type!=4){
			lisan[++cnt]=o[i].var;
		}
	}
	sort(lisan+1,lisan+1+cnt);
	int len=unique(lisan+1,lisan+1+cnt)-lisan;
	buildt(1,1,len+5);
	for(int i=1;i<=n;i++){
		int op=o[i].type,var=o[i].var;
		int wp=lower_bound(lisan+1,lisan+1+len,var)-lisan;
		if(op==1){
			add(1,wp);
		}
		else if(op==2){
			del(1,wp);
		}
		else if(op==3){
			cout<<ask_rank(1,wp)+1<<'\n';
		}
		else if(op==4){
			cout<<lisan[ask_element(1,var)]<<'\n';
		}
		else if(op==5){
			cout<<lisan[ask_pre(1,wp)]<<'\n';
		}
		else if(op==6){
			cout<<lisan[ask_suc(1,wp)]<<'\n';
		}
	}

	return 0;
}

2. 动态开点权值线段树

权值这种东西的范围可以很大,但是对于这种数据结构题再把输入存起来离散化多少有点臃肿,能不能不进行离散化呢?
当然是可以的,这就要用到动态开点线段树 不用我就不开 的思想了。
还是先说 struct 里面存的什么:

struct seg{
	int ls;
	int rs;
	int cnt;
	int no0;
};
int idx=0;
vector<seg> segtree;

下面两个变量的定义同标准的权值线段树,int ls 表示左儿子在 vector 中的下标,int rs 表示右儿子在 vector 中的下标。
idx 表示当前已经分配到第几个下标位置。
这里的动态开点权值线段树用 vector 实现,这种实现方式在需要给每个点都开一个权值线段树且需要合并和前缀和等操作的场景可以极大避免定长数组造成的无用空间情况。
(你可能惊叹于权值树竟然还可以运算和进行前缀和,这些我们在下文讲)。
之后我们在递归时额外在函数传参中记录当前代表的值域区间,即可实现简单的动态开点。

代码时间(可通过点击右侧导航栏跳转)

#define rd read()
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll read(){
	ll x=0,f=1;
	char c=getchar();
	while(c>'9'||c<'0'){if(c=='-') f=-1;c=getchar();}
	while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
const int N=3e5+5;
int cnt=0;
struct seg{
	int ls;
	int rs;
	int cnt;
	int no0;
};
int idx=0;
vector<seg> segtree;
void add(int p,int k,int l,int r){
	if(l==k&&r==k){
		segtree[p].cnt++;
		if(segtree[p].cnt>0) segtree[p].no0=1;
		return;
	}
	int mid=l+r>>1;
	if(k<=mid){
		if(segtree[p].ls==0){
			segtree[p].ls=++idx;
			segtree.push_back(seg{0,0,0,0});
		}
		add(segtree[p].ls,k,l,mid);
	}
	else if(k>mid){
		if(segtree[p].rs==0){
			segtree[p].rs=++idx;
			segtree.push_back(seg{0,0,0,0});
		}
		add(segtree[p].rs,k,mid+1,r);
	}
	segtree[p].cnt=segtree[segtree[p].ls].cnt+segtree[segtree[p].rs].cnt;
	segtree[p].no0=segtree[segtree[p].ls].no0+segtree[segtree[p].rs].no0;
	return;
}
void del(int p,int k,int l,int r){
	if(l==k&&r==k){
		segtree[p].cnt--;
		if(segtree[p].cnt==0) segtree[p].no0=0;
		return;
	}
	int mid=l+r>>1;
	if(k<=mid){
		if(segtree[p].ls==0){
			segtree[p].ls=++idx;
			segtree.push_back(seg{0,0,0,0});
		}
		del(segtree[p].ls,k,l,mid);
	}
	else if(k>mid){
		if(segtree[p].rs==0){
			segtree[p].rs=++idx;
			segtree.push_back(seg{0,0,0,0});
		}
		del(segtree[p].rs,k,mid+1,r);
	}
	segtree[p].cnt=segtree[segtree[p].ls].cnt+segtree[segtree[p].rs].cnt;
	segtree[p].no0=segtree[segtree[p].ls].no0+segtree[segtree[p].rs].no0;
	return;
}
int ask_rank(int p,int k,int l,int r){
	if(l==k&&r==k) return 0;
	if(r<k){
		return segtree[p].cnt;
	}
	int mid=l+r>>1;
	int ls=segtree[p].ls;
	int rs=segtree[p].rs;
	if(mid<k){
		int r1=0;
		int r2=0;
		if(ls==0) r1=0;
		else r1=ask_rank(ls,k,l,mid);
		if(rs==0) r2=0;
		else r2=ask_rank(rs,k,mid+1,r);
		return r1+r2;
	}
	if(k<=mid){
		if(ls==0) return 0;
		else return ask_rank(ls,k,l,mid);
	}
}
int ask_element(int p,int k,int l,int r){
	if(l==r) return l;
	int mid=l+r>>1;
	int ls=segtree[p].ls;
	int rs=segtree[p].rs;
	if(k-segtree[ls].cnt<=0){
		if(ls==0)return -1;
		return ask_element(ls,k,l,mid);
	}
	else{
		if(rs==0)return -1;
		return ask_element(rs,k-segtree[ls].cnt,mid+1,r);
	}
	return -1;
}
int ask_pre(int p,int k,int l,int r){
	if(l==r&&segtree[p].no0&&l!=k){
		return l;
	}
	else if(l==r){
		return -1;
	}
	int mid=l+r>>1;
	int ls=segtree[p].ls;
	int rs=segtree[p].rs;
	if(mid<k&&segtree[rs].no0!=0){
		if(rs==0)return -1;
		int ret=ask_pre(rs,k,mid+1,r);
		if(ret!=-1)return ret;
		else if(ls==0)return -1;
		else return ask_pre(ls,k,l,mid);
	}
	if(mid<k&&segtree[rs].no0==0&&segtree[ls].no0!=0){
		if(ls==0)return -1;
		int ret=ask_pre(ls,k,l,mid);
		return ret;
	}
	if(k<=mid&&segtree[ls].no0!=0){
		if(ls==0)return -1;
		int ret=ask_pre(ls,k,l,mid);
		if(ret!=-1)return ret;
	}
	return -1;
}
int ask_suc(int p,int k,int l,int r){
	if(l==r&&segtree[p].no0&&l!=k){
		return l;
	}
	else if(l==r){
		return -1;
	}
	int mid=l+r>>1;
	int ls=segtree[p].ls;
	int rs=segtree[p].rs;
	if(k<=mid&&segtree[ls].no0!=0){
		if(ls==0)return -1;
		int ret=ask_suc(ls,k,l,mid);
		if(ret!=-1)return ret;
		else if(rs==0)return -1;
		else return ask_suc(rs,k,mid+1,r);
	}
	if(k<=mid&&segtree[ls].no0==0&&segtree[rs].no0!=0){
		if(rs==0)return -1;
		int ret=ask_suc(rs,k,mid+1,r);
		return ret;
	}
	if(mid<k&&segtree[rs].no0!=0){
		if(rs==0)return -1;
		int ret=ask_suc(rs,k,mid+1,r);
		return ret;
	}
	return -1;
}
int main(){

	int n;
	cin>>n;
	segtree.push_back(seg{0,0,0,0});
	segtree.push_back(seg{0,0,0,0});
	idx++;//注意要提前把 vector 添上一个哨兵和根节点不然RE。 
	for(int i=1;i<=n;i++){
		int op,var;
		cin>>op>>var;
		if(op==1){
			add(1,var,-1e7,1e7);
		}
		else if(op==2){
			del(1,var,-1e7,1e7);
		}
		else if(op==3){
			cout<<ask_rank(1,var,-1e7,1e7)+1<<'\n';
		}
		else if(op==4){
			cout<<ask_element(1,var,-1e7,1e7)<<'\n';
		}
		else if(op==5){
			cout<<ask_pre(1,var,-1e7,1e7)<<'\n';
		}
		else if(op==6){
			cout<<ask_suc(1,var,-1e7,1e7)<<'\n';
		}
	}

	return 0;
}

3. 权值线段树的一些性质

把动态开点和线段树合并写完了再来填坑qwq。


last updated on 08/05/2024


迁移自洛谷

posted @ 2025-02-04 13:43  hm2ns  阅读(83)  评论(0)    收藏  举报