主席树入门

今天学习了一下主席树(名字这么强的嘛)
虽然直接理解起来不容易,但是这种解决问题的思想其实并不陌生。
我们可以首先来看维护整个区间第K大的线段树
我们将[l,r]区间内数字的多少用线段树进行维护,这样的话为了求取区间第k大,我们先看左区间有多少个数字,如果左区间就有多于K的数字(或者等于K个),我们直接去左区间找,如果左区间没有K个,我们就去右区间找,找右区间第K-左区间数字个数大的数

void query(int k,int l,int r,int k)
{
	if(l==r) return l;
	int mid=(l+r)>>1;
	if(tree[k<<1].num>=k) query(k<<1,l,mid,k);
	else query(k<<1|1,mid+1,r,k);
}

但是我们要求解的问题没有这么简单,我们需要很方便的求任意区间第K大,比较容易想到的方法是给每个区间建立一个线段树,然后找哪个查哪个,然而这样的时空复杂度显然是不允许的。
我们可以来看另一个问题,就是我们想要很方便地查询任意区间和,我们的第一想法是不是也是将任意区间和计算出来然后输出呢?同样不允许的情况下,我们引入了前缀和,计算从[1…i]的和,然后[l,r]的和就是sum[r]-sum[l-1]
同样的,对于我们想要很方便的求取区间第K大问题,我们也引入前缀和的思想,我们保存从1…n的所有维护[1…i]区间内任意区间数字个数的线段树(就像上面那样),然后想要求解的是[l,r],那么[l,r]区间内任意区间数字个数就是tree[ root[r] ].num-tree[ root[l-1] ].num,然后就能像上面一样求解问题啦
然而,我们需要保存1…n所有维护[1…i]区间内任意区间数字个数的线段树,也是很不容易做到的,但是在每一次更新的时候(多加入一个数字),我们只会修改从这个数字到根节点的不超过logn个节点,其他的节点都是相同的,那么我们不妨就直接用没有改变的节点,再重新申请需要修改的节点。因为一个儿子可能被多个父亲使用,所以这个时候的父亲和儿子节点已经不满足2n和2n+1的关系,我们就需要每个节点保存他的左儿子和右儿子的指针,这里我们用数组下标模拟指针,同时,我们还需要一个root数组保存每个线段树的根节点的指针
对于区间[1…i]的线段树,他的根节点一开始是和前面一个元素的根节点是相同的,左儿子和右儿子以及区间数字的个数也是相同的,然后我们加入一个新元素a[i],如果a[i]在左区间就建立一个新的左儿子节点,这个左儿子一开始也和之前的左儿子相同,我们再更新这个左儿子,直到更新到叶子节点,右儿子同理
代码如下

struct node
{
	int l,r,cnt;
}tree[MAXN];

void insert(int &x,int num,int l,int r)
{
	tree[++t]=tree[x]; tree[t].cnt++; x=t;	//t指向当前可分配空间的数组的下标(相当于新节点的指针)
	if(l==r) return;
	int mid=(l+r)>>1;
	if(num<=mid) insert(tree[x].l,num,l,mid);
	else insert(tree[x].r,num,mid+1,r);
}

查询的话和最上面的代码类似

int query(int i,int j,int k,int l,int r)
{
	if(l==r) return l;
	int tmp=tree[tree[j].l].cnt-tree[tree[i].l].cnt;//左区间的元素个数
	int mid=(l+r)>>1;
	if(k<=tmp)
	{
		return query(tree[i].l,tree[j].l,k,l,mid);
	}
	else
	{
		return query(tree[i].r,tree[j].r,k-tmp,mid+1,r);
	}
}

因为我们需要维护数据范围大小的区间,这可能是做不到的,[-1e-9~1e9]都做不到。因此我们需要进行离散化
离散化代码:

scanf("%d%d",&N,&M);
		for(int i=1;i<=N;i++)
		{
			scanf("%d",&a[i]);
			b[i]=a[i];
		}
		sort(b+1,b+N+1);	//排序
		n=unique(b+1,b+1+N)-b-1;//去重后指向不包含重复元素的末尾的后一个元素(后面都是与前面重复的),n就是不重复元素的个数
		t=0;
		for(int i=1;i<=N;i++)
		{
			a[i]=lower_bound(b+1,b+1+n,a[i])-b;	//a[i]重新赋值为在b[i]的次序,离散化
		}

完整代码:(哦,对了,还要注意开空间,在网上听大佬说得开n*40的空间,巨恐怖)

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<queue>
#include<vector>
#include<set>
#include<string>
#include<cmath>
#include<climits>

using namespace std;

const int MAXN=2000010;
int a[MAXN],b[MAXN],root[MAXN];
int N,M,n,t;
struct node
{
	int l,r,cnt;
}tree[MAXN];

void insert(int &x,int num,int l,int r)
{
	tree[++t]=tree[x]; tree[t].cnt++; x=t;
	if(l==r) return;
	int mid=(l+r)>>1;
	if(num<=mid) insert(tree[x].l,num,l,mid);
	else insert(tree[x].r,num,mid+1,r);
}

int query(int i,int j,int k,int l,int r)
{
	if(l==r) return l;
	int tmp=tree[tree[j].l].cnt-tree[tree[i].l].cnt;
	int mid=(l+r)>>1;
	if(k<=tmp)
	{
		return query(tree[i].l,tree[j].l,k,l,mid);
	}
	else
	{
		return query(tree[i].r,tree[j].r,k-tmp,mid+1,r);
	}
}

int main()
{
	int T,u,v,k;
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d%d",&N,&M);
		for(int i=1;i<=N;i++)
		{
			scanf("%d",&a[i]);
			b[i]=a[i];
		}
		sort(b+1,b+N+1);
		n=unique(b+1,b+1+N)-b-1;
		t=0;
		for(int i=1;i<=N;i++)
		{
			a[i]=lower_bound(b+1,b+1+n,a[i])-b;
		}
		root[0]=tree[0].l=tree[0].r=tree[0].cnt=0;
		for(int i=1;i<=N;i++)
		{
			root[i]=root[i-1];
			insert(root[i],a[i],1,n);
		}
		for(int i=0;i<M;i++)
		{
			scanf("%d%d%d",&u,&v,&k);
			printf("%d\n",b[query(root[u-1],root[v],k,1,n)]);
		}
	}
	return 0;
}

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值