今天学习了一下主席树(名字这么强的嘛)
虽然直接理解起来不容易,但是这种解决问题的思想其实并不陌生。
我们可以首先来看维护整个区间第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;
}