带修主席树模板(P2617)

P2617

 

1、思路:

主席树的思想是通过建立一个区间的前缀和,用root[i]数组记录这个前缀和

每个区间内存储这个区间内有多少个数字比它小,更新时通过新建一个新的节点将之前的节点值存储在

新的节点值中,同时更新这个新的节点值,也将新的节点值变为新的前缀和的节点。

如果要修改这个主席树的一个值,需要修改前缀和即前面所有的前缀和,所以我们可以将这些前缀和用一个

树状数组来维护,以log(n)的复杂度来修改前缀和,再以log(n)的复杂度修改前缀和上的某一个节点。

参考文章

详解

 

2、代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<algorithm>
using namespace std;
const int maxn = 1e5+10;
int root[maxn],lroot[maxn],rroot[maxn],a[maxn],n,m,tot,sz;
struct Node{
	int fg,l,r,x,val;
}cur[maxn];
struct T{
	int l,r,sum;
}Tree[maxn*400];
vector <int> vc;
int lowbit(int x){
	return x&(-x);
}
int getid(int x){
	return lower_bound(vc.begin(),vc.end(),x)-vc.begin()+1;
}
void update(int l,int r,int &rt,int x,int c){
	Tree[++tot] = Tree[rt];
	rt = tot;
	Tree[rt].sum += c;
	if(l==r) return ;
	int mid = (l+r)>>1;
	if(x<=mid) update(l,mid,Tree[rt].l,x,c);
	else update(mid+1,r,Tree[rt].r,x,c);
}
void set(int pos,int x,int c){
	while(pos<=n){ //更新所有的前缀和,这里的root[pos]就是一个树状数组 
		update(1,sz,root[pos],x,c);
		pos += lowbit(pos);
	}
}
int query(int l,int r,int L,int R,int k){
	int lcnt = 0,rcnt = 0;
	for(int i=L-1;i;i-=lowbit(i)){ //1~L-1范围内的修改值 
		lroot[++lcnt] = root[i]; 
	}
	for(int i=R;i;i-=lowbit(i)){ //1~R范围内的修改值 
		rroot[++rcnt] = root[i];
	}
	while(l<=r){ //二分缩小区间 
		if(l==r) return l;
		int mid = (l+r)>>1,res = 0;
		//记录左子树的区间个数 
		for(int i=1;i<=rcnt;i++){ 
			res += Tree[Tree[rroot[i]].l].sum;
		}
		for(int i=1;i<=lcnt;i++){
			res -= Tree[Tree[lroot[i]].l].sum;
		}
		if(k>res){ //在右子区间 
			for(int i=1;i<=rcnt;i++){
				rroot[i] = Tree[rroot[i]].r;
			}
			for(int i=1;i<=lcnt;i++){
				lroot[i] = Tree[lroot[i]].r;
			}
			k -= res;
			l = mid+1;
		}
		else{ //在左子区间 
			for(int i=1;i<=rcnt;i++){
				rroot[i] = Tree[rroot[i]].l;
			}
			for(int i=1;i<=lcnt;i++){
				lroot[i] = Tree[lroot[i]].l;
			}
			r = mid;
		}
	}
}
int main(void){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);
		vc.push_back(a[i]);
	}
	for(int i=1;i<=m;i++){
		char ss[5];
		scanf("%s",ss);
		if(ss[0]=='Q'){
			cur[i].fg = 1;
			scanf("%d%d%d",&cur[i].l,&cur[i].r,&cur[i].val);
		}
		else{
			cur[i].fg = 2;
			scanf("%d%d",&cur[i].val,&cur[i].x);
			vc.push_back(cur[i].x);
		}
	}
	sort(vc.begin(),vc.end());
	vc.erase(unique(vc.begin(),vc.end()),vc.end());
	sz = vc.size();
	
	for(int i=1;i<=n;i++){
		a[i] = getid(a[i]);
	}
	for(int i=1;i<=m;i++)
	if(cur[i].fg==2){
		cur[i].x = getid(cur[i].x);
	}
	for(int i=1;i<=n;i++){
		set(i,a[i],1);
	}
	for(int i=1;i<=m;i++){
		if(cur[i].fg==1){
			printf("%d\n",vc[query(1,sz,cur[i].l,cur[i].r,cur[i].val)-1]);
		}
		else{
			set(cur[i].val,a[cur[i].val],-1);
			set(cur[i].val,cur[i].x,1);
			a[cur[i].val] = cur[i].x;
		}
	}
	return 0;
} 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值