HDU-2665 静态主席树(可持续化线段树入门)

求静态区间第k小


入门链接:
链接一

链接二


模板:

跟大部分线段树一样,建树操作可以省,下面两个版本,一个建了结构体的不熟练版,一个只建数组的熟练版。

#pragma GCC optimize(2)
#include<bits/stdc++.h> 
using namespace std;
typedef long long ll;
const int maxn=1e5+7;
const int mod=1e9+7;
int read(){
    int x=0,f=1;
    char ch=getchar();
    while(ch<'0'||ch>'9'){
        if(ch=='-')f=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9'){
        x=(x<<1)+(x<<3)+ch-'0';
        ch=getchar();
    }
    return x*f;
}
//求静态区间第k小 
struct node{
	int l,r,num;
}sz[maxn<<5];
int T,n,m,cnt,a[maxn],b[maxn],k,rt[maxn];
//rt[i]表示由数组前i个元素组成的线段树的根结点
void update(int &i,int l,int r,int pre,int id){
	i=++cnt;	sz[i]=sz[pre];	sz[i].num++;
	if(l==r)	return;
	int mid=l+r>>1;
	if(id<=mid)	update(sz[i].l,l,mid,sz[pre].l,id);
	else		update(sz[i].r,mid+1,r,sz[pre].r,id);
}
int query(int l,int r,int x,int y,int c){
	if(l==r)	return l;
	int mid=l+r>>1;
	int ans=sz[sz[y].l].num-sz[sz[x].l].num;
	if(ans>=c)	return query(l,mid,sz[x].l,sz[y].l,c);
	else	return query(mid+1,r,sz[x].r,sz[y].r,c-ans);
}
int main(){
	T=read();
	while(T--){
		n=read();	m=read();	cnt=0;
		for(int i=1;i<=n;i++)	b[i-1]=a[i]=read();
		sort(b,b+n);
		k=unique(b,b+n)-b;
		for(int i=1;i<=n;i++){
			int id=lower_bound(b,b+k,a[i])-b;
			update(rt[i],0,k-1,rt[i-1],id);
		}
		while(m--){
			int x,y,c;
			x=read();	y=read();	c=read();
			int ans=query(0,k-1,rt[x-1],rt[y],c);
			printf("%d\n",b[ans]);
		}
	}	
}

/*
1
7 3
1 5 2 6 3 7 4
2 5 3
4 4 1
1 7 3

5
6
3
*/

下面讲解一下我认为学主席树的注意点,帮助理解:

1、L[maxn]和R[maxn]数组不是和平常的线段树一样表示这个区间的左右端点,而是指,假如L[i],R[i],当前第i个节点的左右儿子的位置(序号)

所以query函数中int ans=num[L[y]]-num[L[x]];出来的是 这两个树的区间中有ans个数。(当然也可以右节点相减)

2、关于建树操作为什么可以省,只要一个cnt=0?
(1)我们从rt[1],L[1],R[1]开始建,(中间是i=++cnt),所以rt[0],L[0],R[0]始终为0。我们一直不会动这3个值,所以随着cnt递推,rt[i],L[i],R[i]也随之重置,不用memset各种数组。

(2)在递推rt[1]树时,pre始终为0,你会发现虽然rt[0],L[0],R[0]始终为0,但一直用到这三个值,你可以把这三个值看成一个无限大的0树,但只占一个位置cnt==0。所以数组我们一般从1开始,0这个位置让给这三个值,而区间是(0,k-1)还是(1,k),无区别,任意。就是lower_bound和输入的序号x,y真正表示第几个,稍微有点小修改,根据题意吧。

3、可能一些同学看不懂指针操作,其实不难,就是在递推过程中把 i 这个值顺便附上位置(序号cnt),build和update都要建logn的树,所以都到涉及指针,你实在不会,把函数改成int类,一开始便是rt[0]=build(0,0,k-1),之后便是rt[i]=update(i,0,k-1,rt[i-1],a[i])。最好还是用指针,方便。

#include <bits/stdc++.h>
#pragma GCC optimize(2)
using namespace std;
typedef long long ll;
const int inf=0x3f3f3f3f;
const int mod=1e9+7;
const int maxn=1e5+7;
int read(){
    int x=0,f=1;
    char ch=getchar();
    while(ch<'0'||ch>'9'){
        if(ch=='-')f=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9'){
        x=(x<<1)+(x<<3)+ch-'0';
        ch=getchar();
    }
    return x*f;
}

int n,m,T,rt[maxn],a[maxn],b[maxn],cnt,k;
int num[maxn<<5],L[maxn<<5],R[maxn<<5];

//建树操作可以省
void build(int &i,int l,int r){
	i=++cnt;	num[i]=0;
	if(l==r)	return;
	int mid=l+r>>1;
	build(L[i],l,mid);	build(L[i],mid+1,r);
}

void update(int &i,int l,int r,int pre,int id){
	i=++cnt;	L[i]=L[pre];	R[i]=R[pre];
	num[i]=num[pre]+1;
	if(l==r)	return;
	int mid=l+r>>1;
	if(id<=mid)	update(L[i],l,mid,L[pre],id);
	else		update(R[i],mid+1,r,R[pre],id);
}

int query(int l,int r,int x,int y,int c){
	if(l==r)	return l;
	int mid=l+r>>1;
	int ans=num[L[y]]-num[L[x]];
	if(c<=ans)	return query(l,mid,L[x],L[y],c);
	else	return query(mid+1,r,R[x],R[y],c-ans);
}

int main(){
	T=read();
	while(T--){
		n=read();	m=read();	cnt=0;
		for(int i=1;i<=n;i++)	b[i-1]=a[i]=read();
		sort(b,b+n);
		k=unique(b,b+n)-b;
//		build(rt[0],0,k-1);
		for(int i=1;i<=n;i++){
			int id=lower_bound(b,b+k,a[i])-b;
			update(rt[i],0,k-1,rt[i-1],id);
		}
		while(m--){
			int x,y,z;
			x=read();	y=read();	z=read();
			int ans=query(0,k-1,rt[x-1],rt[y],z);
			printf("%d\n",b[ans]);
		}
	}
}

/*
1 
10 1 
1 4 2 3 5 6 7 8 9 0 
1 3 2 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值