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;
    }

    完结撒花~

  • 相关阅读:
    模板与继承之艺术——空基类优化
    模板的多态(静多态)威力
    Linux在vi编辑模式下如何查找
    PHP var_dump() 函数
    PHP empty() 函数
    IOS连接代理后自动关闭
    charles:屏蔽web网页的抓包信息(proxy)
    Python-unittest单元测试框架总结
    fiddler过滤域名:仅显示指定的域名
    Java中System.getProperties()方法获取的系统属性
  • 原文地址:https://www.cnblogs.com/lbssxz/p/12982250.html
Copyright © 2011-2022 走看看