主席树(可持久化线段树)(区间k大值)//洛谷P3834

第一行——咕咕咕。

第二行——最近是为了祖国的绿色持续种树的职业鸽手。

 

以下为正文:

首先明确:可持久化数据结构就是保留整个操作历史。体现在线段树上,就是在进行修改时,在保留原有的数据的基础上,把更新后的加入(就是新建一部分树)(为什么是新建一部分呢,为了省空间),将更新的点与其相关父节点新建即可。

与普通线段树不同的是:主席树的节点维护的是输入数组在每个节点上的区间内出现的次数(反正按我的理解是这样好的我知道非常拗口)。普通线段树的节点是区间和。

查询:在最初建一个空线段树,每次输入数组值,即新建相关的点进行更新,同时保留原有的节点值,形成一个有n+1个根节点的线段树(n为数组长度)(好的我知道没有图很难理解)(但我不会画图啊委屈.jpg)。它存的是每个前缀的元素出现次数,我们在求区间时,只需作差即可。(我后续会尽量补图的)(咕咕咕)。

其他:1.需要先对输入数组进行离散化。2.主要用于区间求k大值。3.空间复杂度为n*logn,因为不停新建树4.需要先学习离散化和线段树

注:离散化数组可用stl中的unique函数。unique是一个“去重”函数,将重复的元素放到末尾,然后返回一个迭代器的值(但是并没有删除元素,只是将重复元素置于数组末尾)。返回的是最后一个不同元素的下标,减去首元素的地址即可得到离散后数组的大小。

我个人觉得过程很好理解,但怎么放在代码里就这么难受呢?

 

本代码以洛谷P3834为例(其实这份代码是跟洛谷上学的)

具体解释在代码里

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#define ll long long
#define maxn 200005

using namespace std;

int tree[maxn*32] = {0};//每个点的值
int le[maxn*32] = {0};//左右子树
int ri[maxn*32] = {0};
int a[maxn];
int b[maxn];
int cx[maxn];//某种意义上存的是每棵树的根节点,从该点开始遍历可得到一棵新树
int n,cs;
int cnt = 0;

int build(int x,int y)
{
    cnt++;//从1开始存节点
    int rt = cnt;//当前节点编号
    if(x != y)
    {
        int m = (x+y) >> 1;
        le[rt] = build(x,m);
        ri[rt] = build(m+1,y);
    }
    return rt;//返回当前节点的编号
}

int add(int pr,int l,int r,int x)
{
    cnt++;
    int rt = cnt;
    le[rt] = le[pr];
    ri[rt] = ri[pr];
    tree[rt] = tree[pr]+1;//插入的离散后的a[i]在该点代表的区间,所以++
    if(l != r)
    {
        int m = (l+r) >> 1;
        if(x <= m) le[rt] = add(le[pr],l,m,x);
        else ri[rt] = add(ri[pr],m+1,r,x);
    }//判断更新的是左or右子树
    return rt;
}

int query(int l,int r,int x,int y,int k)
{
    if(x == y) return b[x];
    int m = (x+y)>>1;
    int rt = tree[le[r]] - tree[le[l]];
    //传过来的是cx[l-1]和cx[r]
    //tree存的是1~l-1和1~r的出现次数
    //作差即为区间l~r
    if(rt >= k) return query(le[l],le[r],x,m,k);//左子树
    else return query(ri[l],ri[r],m+1,y,k-rt);//右子树
    //在右子树(本来就比左子树大了)找第k-rt小的值
}//l,r区间的第k小,x,y为当前区间

int main()
{
    scanf("%d%d",&n,&cs);
    for(int i = 1; i <= n; ++i)
        scanf("%d",&a[i]),b[i] = a[i];
    sort(b+1,b+n+1);
    int siz = unique(b+1,b+n+1)-b-1;//对数据进行去重
    cx[0] = build(1,siz);//建立一棵空树
    for(int i = 1; i <= n; ++i)
    {
        int q = lower_bound(b+1,b+siz+1,a[i])-b;//实际上是按a[i]的大小进行标号
        //-b可以做到标号从1开始
        cx[i] = add(cx[i-1],1,siz,q);//建新树,并把根节点赋给cx数组
    }
    while(cs--)
    {
        int l,r,k;
        scanf("%d%d%d",&l,&r,&k);
        int ans = query(cx[l-1],cx[r],1,siz,k);//查询的是第cx[l]开始的树所以是传cx[l-1]
        //因为函数中会删去
        printf("%d\n",ans);
    }
    return 0;
}

 

写这份代码的心路历程:

//我个人觉得结构体版的思路更清晰一些,所以先用这个写一下
//线段树我是用数组写的,我会再写一个数组版本的
//我不会说是因为数组版的代码量小一些
//我收回我的话结构体版一点都不清晰
//代码快要给爸爸看吐了
//很好我回归数组版了

 

应该会补其他练习题的!

欢迎指出错误qwq

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值