zoukankan      html  css  js  c++  java
  • 洛谷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

    对于 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 imes 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
    ",ys[query(root[l-1],root[r],1,num,x)]);
        }
        return 0;
    }

    完结撒花~

  • 相关阅读:
    net core 使用 rabbitmq
    asp.net core WebApi 返回 HttpResponseMessage
    asp.net core 2.1 WebApi 快速入门
    JQuery EasyUI combobox动态添加option
    php截取字符去掉最后一个字符
    JQuery EasyUI Combobox的onChange事件
    对于不返回任何键列信息的 selectcommand 不支持 updatecommand 的动态 sql 生成
    Access2007 操作或事件已被禁用模式阻止解决办法
    Easyui 中 Tabsr的常用方法
    Win 7 IE11不能下载文件,右键另存为也不行
  • 原文地址:https://www.cnblogs.com/lbssxz/p/12982250.html
Copyright © 2011-2022 走看看