题目背景
这是个非常经典的主席树入门题——静态区间第K小
数据已经过加强,请使用主席树。同时请注意常数优化
题目描述
如题,给定N个正整数构成的序列,将对于指定的闭区间查询其区间内的第K小值。
输入输出格式
输入格式:第一行包含两个正整数N、M,分别表示序列的长度和查询的个数。
第二行包含N个正整数,表示这个序列各项的数字。
接下来M行每行包含三个整数 l,r,k, 表示查询区间 [l,r]内的第k小值。
输出格式:输出包含k行,每行1个正整数,依次表示每一次查询的结果
输入输出样例
5 5
25957 6405 15770 26287 26465
2 2 1
3 4 1
4 5 1
1 2 2
4 4 1
6405
15770
26287
25957
26287
说明
数据范围:
对于20%的数据满足: 1≤N,M≤10
对于50%的数据满足: 1≤N,M≤103
对于80%的数据满足: 1≤N,M≤105
对于100%的数据满足: 1≤N,M≤2⋅105
对于数列中的所有数 ai ,均满足 −109≤ai≤109
样例数据说明:
N=5,数列长度为5,数列从第一项开始依次为 [25957,6405,15770,26287,26465]
第一次查询为 [2,2] 区间内的第一小值,即为6405
第二次查询为 [3,4]区间内的第一小值,即为15770
第三次查询为 [4,5]区间内的第一小值,即为26287
第四次查询为 [1,2]区间内的第二小值,即为25957
第五次查询为 [4,4] 区间内的第一小值,即为26287
Solution:
补坑了——很久很久以前的老坑。(差点没想起自己还有这个模板没写博客`~`)
主席树类似于权值线段树(或者说就是多棵权值线段树和起来,因为以前介绍过权值线段树,所以这里概念不讲了~)。
对于整个区间的第$k$大查询,一个很简单的思路就是对原序列离散,然后维护一颗权值线段树,每次查询子树中的出现的数值个数,和$k$比对递归到叶子节点就好了($sum[rt]>=k$查左子树$sum[rt<<1]$与$k$比对,$sum[rt]<k$查右子树$sum[rt<<1|1]$比对$k-sum[rt<<1]$)。
那么推及任意一段区间,由于权值线段树只能维护一整段的区间,所以我们可以利用前缀和的思想,维护$n$棵权值线段树(第$i$棵维护区间$[1,i]$),那么每次查询时我们直接用$sum[r]-sum[l-1]$就是区间$[l,r]$的子树的数值出现的个数。
思路是很简单,但是我们发现这样写需要建$n$棵线段树,$n^2$的空间显然开不下。
于是,神犇$HJT$(野史:称之“主席”,因为其名简写和某位国家前领导人一样),发明了$nlogn$的建树方法。
我们发现从已经建好的$[1,i]$区间的树,推及到$[1,i+1]$建树时,改变的只是$[1,i]$区间所建的权值线段树从根节点到某一叶子节点的路径上的节点信息,而其它节点都没有被改变。
那么就好搞了,记录每棵树的根节点,然后每新建一棵树(等价于在前面的线段树中使某个数的值加$1$),若当前节点信息不需要修改则直接从当前根节点向上一棵树的该节点连边,若被修改了,则新建一个节点保存被修改的信息。
最后就是比较简单的查询了。(唯一需要注意的是,主席树维护的是权值线段树,所以每个点上的权值需要离散,我的离散过程是直接模拟,略丑)。
代码:
#include<bits/stdc++.h> #define il inline #define ll long long #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 #define For(i,a,b) for(int (i)=(a);(i)<=(b);(i)++) using namespace std; const int N=200005; int n,m,rt[N],rank[N],cnt,w[N],tot; struct num{ int v,id; bool operator<(const num a)const{return v==a.v?id<a.id:v<a.v;} }a[N]; struct node{ int ls,rs,sum; }t[N*40]; il int gi(){ int a=0;char x=getchar();bool f=0; while((x<'0'||x>'9')&&x!='-')x=getchar(); if(x=='-')x=getchar(),f=1; while(x>='0'&&x<='9')a=(a<<3)+(a<<1)+x-48,x=getchar(); return f?-a:a; } il void build(int l,int r,int &rt){ rt=++tot; t[rt].sum=0; if(l==r)return; int m=l+r>>1; build(l,m,t[rt].ls); build(m+1,r,t[rt].rs); } il void update(int l,int r,int c,int lst,int &rt){ rt=++tot; t[rt].ls=t[lst].ls; t[rt].rs=t[lst].rs; t[rt].sum=t[lst].sum+1; if(l==r)return; int m=l+r>>1; if(c<=m)update(l,m,c,t[lst].ls,t[rt].ls); else update(m+1,r,c,t[lst].rs,t[rt].rs); } il int query(int L,int R,int l,int r,int k){ if(l==r)return l; int m=l+r>>1; int ret=t[t[R].ls].sum-t[t[L].ls].sum; if(k<=ret)return query(t[L].ls,t[R].ls,l,m,k); else return query(t[L].rs,t[R].rs,m+1,r,k-ret); } int main(){ n=gi(),m=gi(); For(i,1,n)a[i].v=gi(),a[i].id=i; sort(a+1,a+n+1); rank[a[1].id]=++cnt;w[cnt]=a[1].v; For(i,2,n) if(a[i].v!=a[i-1].v)w[++cnt]=a[i].v,rank[a[i].id]=cnt; else rank[a[i].id]=cnt; build(1,cnt,rt[0]); For(i,1,n)update(1,cnt,rank[i],rt[i-1],rt[i]); int x,y,z; while(m--){ x=gi(),y=gi(),z=gi(); int ans=query(rt[x-1],rt[y],1,cnt,z); printf("%d ",w[ans]); } return 0; }