[P2617]Dynamic Rankings(树状数组+主席树)

博客围绕带修区间第k小问题展开。对于静态区间第k小,可离散化后用主席树维护前缀和,修改前缀和可用树状数组。将树看作区间上的点,可用树套树解决带修区间第k小问题,还介绍了离散化、修改及查询操作的具体做法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

求带修区间第k小。如果是静态的区间第k小,可以离散化后建主席树维护一个前缀和,查询时在线段树上差分一下就能得到一段区间内某范围点的个数,判断是跳到左子树还是右子树;如果是修改前缀和,可以用树状数组。那么要是把一棵树看作区间上一个点,似乎就可以树套树来做带修的区间第k小了。

首先做一下离散化,然后从1到n来用树状数组添加每个点的影响,就相当于把树状数组上的一次加法变成线段树上的一次单点修改。然后每个修改操作和初始化时差不多;每个查询操作,先把一会儿要统计的所有树的根节点编号记录一下,然后还是在两棵线段树之间差分求点数,只是每向下跳一次都要把记录的所有节点都向下跳一次,统计答案时也都要统计一遍,具体方法和树状数组的查询一样。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=200010;
struct node{
	int l,r,x;
}tree[N*200];
struct num{
	int a,b,id;
}a1[N];
struct opt{
	int op,a,b,c;
}q1[N];
char str1[5];
int n,m,cnt1,cnt2,num1,lnum,rnum,v[N],l1[N],l2[N],root[N],tid[N];
inline int read(){
	int f=0,x=0;
	char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return f?-x:x;
}
bool cmp1(num i,num j){
	return i.a<j.a;
}
bool cmp2(num i,num j){
	return i.id<j.id;
}
void add2(int &p,int pa,int pb,int x,int f){
	if(!p){p=++num1;tree[p].l=tree[p].r=0;}
	tree[p].x+=f;
	if(pa==pb)return;
	int mid=(pa+pb)>>1;
	if(x<=mid)add2(tree[p].l,pa,mid,x,f);
	else add2(tree[p].r,mid+1,pb,x,f);
}
inline int lowbit(int x){
	return x&(-x);
}
inline void add1(int x,int p,int f){
	while(x<=n){
		add2(root[x],1,cnt2,p,f);
		x+=lowbit(x);
	}
}
int check1(int l,int r,int k){
	if(l==r)return l;
	int mid=(l+r)>>1,sum=0;
	for(int i=1;i<=lnum;i++)sum-=tree[tree[l1[i]].l].x;
	for(int i=1;i<=rnum;i++)sum+=tree[tree[l2[i]].l].x;
	if(sum>=k){
		for(int i=1;i<=lnum;i++)l1[i]=tree[l1[i]].l;
		for(int i=1;i<=rnum;i++)l2[i]=tree[l2[i]].l;
		return check1(l,mid,k);
	}else{
		for(int i=1;i<=lnum;i++)l1[i]=tree[l1[i]].r;
		for(int i=1;i<=rnum;i++)l2[i]=tree[l2[i]].r;
		return check1(mid+1,r,k-sum);
	}
}
inline void pre(int l,int r){
	while(l){l1[++lnum]=root[l];l-=lowbit(l);}
	while(r){l2[++rnum]=root[r];r-=lowbit(r);}
}
int main(){
	n=read();m=read();
	for(int i=1;i<=n;i++){a1[i].a=read();a1[i].id=i;}
	cnt1=n;
	for(int i=1;i<=m;i++){
		scanf("%s",str1);
		if(str1[0]=='Q'){q1[i].op=0;q1[i].a=read();q1[i].b=read();q1[i].c=read();}
		else{q1[i].op=1;q1[i].a=read();q1[i].b=a1[++cnt1].a=read();a1[cnt1].id=cnt1;}
	}
	sort(a1+1,a1+cnt1+1,cmp1);
	cnt2=0;a1[1].b=v[a1[1].id]=++cnt2;tid[1]=a1[1].a;
	for(int i=2;i<=cnt1;i++){
		if(a1[i].a!=a1[i-1].a){cnt2++;tid[cnt2]=a1[i].a;}
		a1[i].b=cnt2;v[a1[i].id]=cnt2;
	}
	sort(a1+1,a1+cnt1+1,cmp2);
	root[0]=0;tree[0].x=tree[0].l=tree[0].r=0;
	for(int i=1;i<=n;i++)add1(i,v[i],1);
	for(int cnt3=n,i=1;i<=m;i++){
		if(q1[i].op==0){
			lnum=rnum=0;pre(q1[i].a-1,q1[i].b);
			printf("%d\n",tid[check1(1,cnt2,q1[i].c)]);
		}else{
			add1(q1[i].a,v[q1[i].a],-1);
			v[q1[i].a]=a1[++cnt3].b;
			add1(q1[i].a,v[q1[i].a],1);
		}
	}
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值