求区间第K大的值;
我们需要在短时间内回答数目巨大的问题,这个算法的核心是空间换时间;
每个点建一个线段树,是的;
我们先离散化所有权值,使得当前的权值在1到n范围内,恰巧是vector里的下标;
对于每一个点,我们分成左二子和右儿子,分别存放当前区间的左半部分和右半部分,维护左右节点的数量;
我们怎么求区间第K大呢?
运用前缀和的思想,我们其实建出来的很多树左右数量是递增的;
遍历每一个节点,现将当前节点继承前一节点的历史状态,再将节点数+1,根据当前节点的大小判断插在左边还是右边,递归进行此操作;
对于区间【l,r】,我们将l-1的树拿出来,r的树拿出来,每个树的节点数就是1到 i 的节点数,先两个左子树的节点数相减,得到的数与当前k比较,
如果大于k就在左子树里找,如果小于k就在右子树里找k-sum_L个;也是递归实现;
实质就是每次加点,查询时套用前缀和,根据数量判断向下递归的方向,从而减少时间复杂度;
时刻注意每个点代表的历史状态,谁和谁对应;
离散化操作
sort(v.begin(),v.end());
v.erase(unique(v.begin(),v.end()),v.end());
先将数组排序,然后unique(),此时函数操作是将重复的数字都扔到后面,返回不重复序列的最后一个数的下一个数的下标;这时我们将后面删去即可;
取得离散后的数lower_bound即可;
根据学长的传承,空间开40倍;
section (区间)
#include<cstdio> #include<vector> #include<cstring> #include<algorithm> using namespace std; const int maxn=2e5+10; vector<int> v; int root_sec[maxn]; struct node { int l,r,sum_pos; }t[maxn*40]; int get_id(int x) { return lower_bound(v.begin(),v.end(),x)-v.begin()+1; } int n,m,a[maxn],cnt; void update(int l,int r,int x,int &y,int pos) { y=++cnt;t[y]=t[x];t[y].sum_pos++; if(l==r) return ; int mid=(l+r)>>1; if(pos<=mid) update(l,mid,t[x].l,t[y].l,pos); else update(mid+1,r,t[x].r,t[y].r,pos); } int query_id(int l,int r,int x,int y,int sum_k) { if(l==r) return l; int sum_sec=t[t[y].l].sum_pos-t[t[x].l].sum_pos; int mid=(l+r)>>1; if(sum_sec>=sum_k) return query_id(l,mid,t[x].l,t[y].l,sum_k); else return query_id(mid+1,r,t[x].r,t[y].r,sum_k-sum_sec); } int main() { scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) { scanf("%d",&a[i]); v.push_back(a[i]); } sort(v.begin(),v.end()); v.erase(unique(v.begin(),v.end()),v.end()); for(int i=1;i<=n;i++) update(1,n,root_sec[i-1],root_sec[i],get_id(a[i])); for(int i=1;i<=m;i++) { int l,r,k; scanf("%d%d%d",&l,&r,&k); printf("%d ",v[query_id(1,n,root_sec[l-1],root_sec[r],k)-1]); } return 0; }
至于为什么叫主席树,是因为发明他的人叫HJT;