洛谷P3834 【模板】可持久化线段树 1 题解

关于可持久化线段树的入门,点这里


这是个非常经典的主席树入门题——静态区间第 kk 小

数据已经过加强,请使用主席树。同时请注意常数优化

题目描述

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

输入格式

第一行包含两个正整数 n,mn,m,分别表示序列的长度和查询的个数。

第二行包含 nn 个整数,表示这个序列各项的数字。

接下来 mm 行每行包含三个整数 l, r, kl,r,k , 表示查询区间 [l, r][l,r] 内的第 kk 小值。

输出格式

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

输入输出样例

输入 #1
  5 5
25957 6405 15770 26287 26465 
2 2 1
3 4 1
4 5 1
1 2 2
4 4 1
输出 #1
6405
15770
26287
25957
26287

说明/提示

数据范围:

对于 20\%20% 的数据满足:1 \leq n,m \leq 101n,m10

扫描二维码关注公众号,回复: 11286246 查看本文章

对于 50\%50% 的数据满足:1 \leq n,m \leq 10^31n,m103

对于 80\%80% 的数据满足:1 \leq n,m \leq 10^51n,m105

对于 100\%100% 的数据满足:1 \leq n,m \leq 2\times 10^51n,m2×105

对于数列中的所有数 a_iai,均满足 -{10}^9 \leq a_i \leq {10}^9109ai109

样例数据说明:

n=5n=5,数列长度为 55,数列从第一项开始依次为[25957, 6405, 15770, 26287, 26465 ][25957,6405,15770,26287,26465]

第一次查询为[2, 2][2,2]区间内的第一小值,即为 6405

第二次查询为 [3, 4][3,4] 区间内的第一小值,即为 15770

第三次查询为 [4, 5][4,5] 区间内的第一小值,即为 26287

第四次查询为 [1, 2][1,2] 区间内的第二小值,即为 25957

第五次查询为 [4, 4][4,4] 区间内的第一小值,即为 26287

 对于部分符号想要看清楚的,原题目链接


找思路

看完题目,我们可以想到:

level.1最朴素方法:将原序列存在一个数组里,对于每一个询问,我们都对其中的元素进行排序,之后查找。

很明显以上方法由于太过朴ruo素zhi,复杂度炸的死死的。

level.2权值线段树做法:

什么是权值线段树?

简单来说,权值线段树维护一列数中数的个数,是线段树的一种变形。对于每一个节点,我们可以用它来维护一个区间内有多少个值为k的数。

举个例子:

我们有数列1 2 4 3 2 1 4

数一数其中有2个1,2个2,1个3,2个4

于是我们可以建立这样的一个权值线段树:

 每一个节点代表的区间是取值范围,节点的权值代表在节点表示范围内有多少个数。

理解权值线段树后,我们考虑这样一个思路:

1.因为题目数据跨范围过大,我们不得不将其离散化,离散化后我们便获得了数与排名的对应关系,以及一共有多少个大小不同的数,记不同大小的数的总数为num,记a[i]为原数列第i个数是第几小。

2.一个数一个数考虑,对于离散后的数列a,我们从前往后扫,每扫到一个数,我们都建立一棵权值线段树,树上记录的信息是:数列a上从编号1到编号i这一个数列。

比如,现在我们i等于3,前三个数分别是1 2 4(排名第一小,第二小,第四小)有1个1,1个2,1个4,我们建立一棵权值线段树:

 长这样。

然后i等于7时,数列1 2 4 3 2 1 4,为我们就建立了一棵上面例子的权值线段树:

...........

对于每一个a[i]我们亦是如此;

3.要获取[l,r]的元素信息,假如l=4,r=7,我们可以这样做:

用i=7时建立的线段树减去i=4时的线段树。具体相减方法是对应节点的权值相减。

按照上述相减方法进行操作后,我们就成功得到了这样的一棵权值线段树:

 对于这棵权值线段树的信息解读:他表示一个数列,这个数列有1个1,1个2,1个3,1个4.

再看看原序列中[4,7]的信息:3 2 1 4

1个3,1个2,1个1,1个4。

完美获得区间信息。

4.我们最后要做的工作是获取区间第k小的数的值。对此,我们可以利用dfs查找:

以上面相减获得的权值线段树为例,我们要获取第3小。

从根节点开始,根节点的左儿子权值为2,比3小,说明我们要查找的元素在右子树里;

由于我们是从小往大数,但是在进入右儿子后,我们忽略了左子树上节点的个数(左子树都比我们要找的节点小),因此我们要减去左子树的size,然后继续查找:3-2=1;

左儿子大小为1,说明要找的元素在左子树里,直接进入左子树;

我们发现l==r==3,因此说明我们要找的元素找到了,是第3小的数。于是我们return,逆映射后输出即可。

level.3主席树优化:

很明显,上述做法可行,但是空间会炸掉。对于一堆线段树,我们自然要用主席树进行整合利用空间。套上主席树模板即可。在此不再赘述,了解主席树基本原理的去文章开头,想知道做法的看代码即可。


(呼~)
code:
 
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 200007 
using namespace std;
int read()//本人祖传快读
{
    int ans=0;
    char ch=getchar(),last=' ';
    while(ch<'0'||ch>'9')last=ch,ch=getchar();
    while(ch>='0'&&ch<='9')ans=(ans<<3)+(ans<<1)+ch-'0',ch=getchar();
    return last=='-'?-ans:ans;
}
int top,root[N],ys[N],a[N],n,m;//a[i]表示第i个元素为第几小,ys[i]表示第i小的元素值是多少
struct lshnode{//离散化用
    int data,id;
}q[N];
struct node{//朱希树用(雾)
    int siz,ls,rs;
}tree[N*25];
bool cmp(lshnode a,lshnode b)//离散化cmp比较函数
{
    return a.data<b.data;
}
int build(int l,int r)//第一棵权值线段树,节点值均为空
{
    int now=++top;
    if(l==r)
    {
        return now;
    }
    int mid=(l+r)>>1;
    tree[now].ls=build(l,mid);
    tree[now].rs=build(mid+1,r);
    return now;
}
int update(int now,int l,int r,int x)//随着一个一个元素的考虑而建树
{
    int p=++top;
    tree[p]=tree[now];
    tree[p].siz++;//新加入一个节点,一路上size都要加一
    if(l==r)
    {
        return p;
    } 
    int mid=(l+r)>>1;
    if(x<=mid)
        tree[p].ls=update(tree[now].ls,l,mid,x);
    else
        tree[p].rs=update(tree[now].rs,mid+1,r,x);
    return p;
}
int query(int u,int v,int l,int r,int x)//u,v从l-1,r来的 
{
    if(l==r)return l;//找到第k小了 
    int delta=tree[tree[v].ls].siz-tree[tree[u].ls].siz;//只考虑左子树size即可
    int mid=(l+r)>>1;
    if(x<=delta)//在左子树里面
        return query(tree[u].ls,tree[v].ls,l,mid,x);//下一层查找
    else
        return query(tree[u].rs,tree[v].rs,mid+1,r,x-delta);
}
int main(){
    n=read(),m=read();
    for(int i=1;i<=n;i++)
        q[i].data=read(),q[i].id=i;
    sort(q+1,q+1+n,cmp);
    //我们需要映射:第k小的数是多少,第k个数是第几小 
    q[0].data=1000000007;//鬼知道干什么hhh
    int num=0;//离散化后的 不同数 的个数
    for(int i=1;i<=n;i++)
    {
        if(q[i].data!=q[i-1].data)num++;
        a[q[i].id]=num;
        ys[num]=q[i].data;
    }//以上离散化 
    root[0]=build(1,num);
    for(int i=1;i<=n;i++)
        root[i]=update(root[i-1],1,num,a[i]);
    for(int i=1,l,r,x;i<=m;i++)
    {
        l=read();r=read();x=read();
        printf("%d\n",ys[query(root[l-1],root[r],1,num,x)]);
    }
    return 0;
}

完结撒花~

猜你喜欢

转载自www.cnblogs.com/lbssxz/p/12982250.html
今日推荐