题目链接

(模板)可持久化线段树 1(主席树)

题目背景

这是个非常经典的主席树入门题——静态区间第$k$小
数据已经过加强,请使用主席树。同时请注意常数优化

题目描述

如题,给定$n$个整数构成的序列,将对于指定的闭区间查询其区间内的第$k$小值。

输入格式

第一行包含两个正整数$n,m$,分别表示序列的长度和查询的个数。
第二行包含$n$个整数,表示这个序列各项的数字。
接下来$m$行每行包含三个整数$l,r,k$,表示查询区间$[l,r]$内的第$k$小值。

输出格式

输出包含$k$行,每行一个整数,依次表示每一次查询的结果

样例输入

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 \leq n,m \leq 10$
对于$50\%$的数据满足:$1 \leq n,m \leq 10^3$
对于$80\%$的数据满足:$1 \leq n,m \leq 10^5$
对于$100\%$的数据满足:$1 \leq n,m \leq 2\times 10^5$
对于数列中的所有数$a_i$,均满足$-{10}^9 \leq a_i \leq {10}^9$

样例数据说明:

$n=5$,数列长度为$5$,数列从第一项开始依次为$[25957,6405,15770,26287,26465]$
第一次查询为$[2,2]$区间内的第一小值,即为$6405$
第二次查询为$[3,4]$区间内的第一小值,即为$15770$
第三次查询为$[4,5]$区间内的第一小值,即为$26287$
第四次查询为$[1,2]$区间内的第二小值,即为$25957$
第五次查询为$[4,4]$区间内的第一小值,即为$26287$

题解

题意:给一个序列有$m$个询问,求区间第$k$大。
既然这是一道主席树板题,那么我就详细讲一下主席树吧。(当然还有其他方法过此题,但我也就只会打主席树了)
众所周知,主席树是一种奇怪的线段树,结合了前缀和思想,那么我们考虑这样一种想法。
我们用$n$棵线段树,第$i$棵线段树表示队列$1$~$i$的信息。
我们用节点的$value$表示$1$~$i$中值在区间内的数的个数。
但是这样的话区间就会很大,那么我们离散化一下就好了。
那么我们就会得到$n$棵形状相同的树:(画画不是很好,尽请谅解)

然后我们考虑一下如果第$i$棵树减去第$j$棵树(两棵树对应节点的$value$相减),那么我们得到的就是$i+1$~$j$这段区间的线段树了。
所以我们每次新建一棵树的时候就要记录一下这棵树的根节点。
这样我们就能在$O(logn)$的时间复杂度下完成每个询问了,具体操作如下(感觉和平衡树求第$k$大差不多):
从根节点开始搜索,如果$k$小于等于左子树的$value$,那么第$k$大一定在左子树的区间内,往左边搜,
否则的话就往右边搜,然后把$k$的值减去左子树的$value$,
当搜到区间内只有一个数(也就是区间的$left==right$),那么这个数就是第$k$大的数了。

<center>--------------------------------------------------------------------------优雅的分界线--------------------------------------------------------------------------</center>
接下来是重点:
如果按照上面说的方法去建树,那么会发现空间会超大,而且建图的时间复杂度就是$O(n^2)$了,那么接下来就是主席树的关键部分了。
我们考虑到当我们已经求完$1$~$i$的线段树后,思考一下和下一棵线段树相比,只是比这棵线段树多了一个点,
假设加入的数为$x$,那么对于树来说,只要点$(x,x)$的所有祖先的$value+1$就好了,
那么我们就想到对于那些不变的点,就不要新建一棵子树了,直接把下一棵数的点连到上一棵树上就好了,这样能节省很多空间(但是还是很费空间,所以用主席树前要想清楚空间是否够用)。
如下图所示:

图中增加的数字是$2$,这样建图每次就只需要增加$logn$个点了,能节省很多空间和时间。

个人认为理解了主席树的思想后实现就比较简单了。
上代码:

#include<bits/stdc++.h>
using namespace std;
int n,m;
int a[200009],to[200009],ans[200009];
struct bb{
    int s,x;
}b[200009];
int rt[200009];
int l,r,k;
struct aa{
    int l,r;
    int s;
    int cd[2];
}p[5000009];
int len;
bool cmp(bb x,bb y){return x.s<y.s;}
void init(int u,int l,int r){
    p[u].l=l;p[u].r=r;
    p[u].s=0;
    if(l==r) return;
    int mid=(l+r)/2;
    if(l<=mid) init(p[u].cd[0]=++len,l,mid);
    if(mid+1<=r) init(p[u].cd[1]=++len,mid+1,r);
}
void dfs(int u,int x){
    p[u].s++;
    if(p[u].l==p[u].r) return;
    int mid=(p[u].l+p[u].r)/2;
    len++;
    if(x<=mid){
        p[len]=p[p[u].cd[0]];
        p[u].cd[0]=len;
    }else{
        p[len]=p[p[u].cd[1]];
        p[u].cd[1]=len;
    }
    dfs(len,x);
}
void ask(int lu,int ru,int k){
    if(p[lu].l==p[lu].r){
        printf("%d\n",ans[p[lu].l]);
        return;
    }
    if(k<=p[p[ru].cd[0]].s-p[p[lu].cd[0]].s) ask(p[lu].cd[0],p[ru].cd[0],k);
    else ask(p[lu].cd[1],p[ru].cd[1],k-(p[p[ru].cd[0]].s-p[p[lu].cd[0]].s));
}
int kk;
int main(){
    scanf("%d%d",&n,&m);
    for(int j=1;j<=n;j++){
        scanf("%d",&a[j]);
        b[j].s=a[j];
        b[j].x=j;
    }
    sort(b+1,b+n+1,cmp);
    for(int j=1;j<=n;j++){
        if(j!=1 && b[j].x==b[j-1].x) k++;
        to[b[j].x]=j-k;
        ans[j-k]=b[j].s;
    }
    init(rt[0]=len=1,1,n-k);
    for(int j=1;j<=n;j++){
        rt[j]=++len;
        p[len]=p[rt[j-1]];
        dfs(len,to[j]);
    }
    for(int j=1;j<=m;j++){
        scanf("%d%d%d",&l,&r,&k);
        ask(rt[l-1],rt[r],k);
    }
    return 0;
}