首页 > 其他分享 >可持续化线段树

可持续化线段树

时间:2023-05-16 21:02:53浏览次数:35  
标签:sz 插入 持续 线段 tr leq ll

可持续化线段树

前言:
“这个数据结构是属于比较抽象的一类。并且代码实现比较繁琐复杂。”
别人都这么说,我却觉得挺好理解、也挺好写的(可能是因为我曾经与多道线段树毒瘤题抗争多次)
为了避免以后我突然脑子抽了不记得了,可以拿出来看看。所以写下这篇笔记,希望也能帮到大家。

建议:带上一个清晰的脑子(草稿纸和笔是没有用的,因为理解过程中会用三维的叠加操作)

注:本篇文章是我这个小蒟蒻写的,真正的dalao请看个玩笑便好,不必争论对错(但是欢迎指出文章存在的小错误)。

什么是可持续化线段树

可持续化线段树,就是一种能保存历史版本信息的线段树
“可持续化”——可以返回之前的某个状态,并在该基础上进行修改
可持续化线段树就是这样的一种结构。

为什么要用可持续化线段树

我们先考虑一个一般场景:

此时你要实现一个可持久化的线段树,你会怎么做?

一般人想到的、最朴素的做法就是每一次操作新建一棵线段树。

但是无论从空间还是时间上看,这种算法都是一种非常差的算法。

仔细思考:

我们每次更新肯定不会一整棵树进行修改,那么我们每次都要新建一棵树吗?

显然不用。所以我们无需重新建树。

什么是主席树

主席树是可持续化线段树的一种。

因为这种结构由黄嘉泰同学发明,而名字拼音缩写又恰好与曾经的一位提出科学发展观的主席的名字拼音缩写相同,所以称为主席树

主席树是以时间轴将(不同时刻状态的线段树的)根节点串起来的多层的权值线段树
每一层的根节点都代表一个历史时刻的入口,每一层可以共用之前层的节点。

为什么要使用主席树

主席树最初发明时,是为了解决区间第\(k\)小的问题。

主席树

主席树是权值线段树(上文有提),那么插入数据的时候,我们将得到一条链。

例如我们数组元素的值域在\([1,10^3]\)之间,插入的第一个数据是\(100\),那通过“往左往右”(类似分治)的方法就可以找到代表\(100\)的叶子节点。
详见图一:

而类似的,我们在下一次插入新数据的时候,也会得到一条从根节点到叶子结点的一条链。
当插入\(666\)时,如图二:

注意!

此时我们已经插入了两次数据,那么我们在插入第二次数据的同时,也要将第二棵树某些节点连向第一棵树“已开拓的边”。
也就是说,插入第二次数据后,我们得到的线段树不会仅仅是上图的一条链,而是如图三所示的一棵树:

一个结论

那么我们可以将主席树简单理解成一个可以排开竖着放小玻璃片的暗箱。然后每一次插入数据,可以理解成每次放一张小玻璃片(按插入时间顺序依次有序放),而小玻璃片上面印着从根到叶子节点的链的图案。

当我们想查询第\(n\)次的线段树状态时,可以理解成在第\(n\)张与第\(n+1\)张玻璃片之间放一个不透光的白布;然后用一束光从第一张玻璃片打过去,在白布上呈现的图案,就是第\(n\)次历史时刻的图案。

如图四:

那么主席树的实现原理也就搞清楚了,可以挂代码了。

主席树代码实现

插入操作

code:

#define ll long long
void update(ll r)
{
    tr[r].sz=tr[tr[r].lc].sz+tr[tr[r].rc].sz;
}
void ins(ll &rt,ll lst,ll l,ll r,ll val)
{
    rt=++tot;
    tr[rt]=tr[lst];
    if(l==r)
        tr[rt].sz++;
    else
    {
        ll mid=(l+r)>>1;
        if(val<=mid)
            ins(tr[rt].lc,tr[lst].lc,l,mid,val);
        else
            ins(tr[rt].rc,tr[lst].rc,mid+1,r,val);
        update(rt);
    }
}

查询操作

code:

ll query(ll r1,ll r2,ll l,ll r,ll k)
{
    if(l==r)
        return l;
    ll lc1=tr[r1].lc,lc2=tr[r2].lc;
    ll mid=(l+r)>>1;
    ll tmp=tr[lc2].sz-tr[lc1].sz;
    if(tmp>=k)
        return query(lc1,lc2,l,mid,k);
    return query(tr[r1].rc,tr[r2].rc,mid+1,r,k-tmp);
}

main函数

code:

const ll INF=1e3;
#define rp(i,o,p) for(ll i=o;i<=p;++i)
int main()
{
    scanf("%lld%lld",&n,&m);
    rp(i,1,n)
    {
        scanf("%lld",&a[i]);
        ins(rts[i],rts[i-1],1,INF,a[i]);
    }
    rp(i,1,m)
    {
        ll x,y,z;
        scanf("%lld%lld%lld",&x,&y,&z);
        printf("%lld\n",query(rts[x-1],rts[y],1,INF,z));
    }
    return 0;
}

板题&后记

主席树还是挺好学的,主要是要抽象出那个模型就好了。
本片题解写得比较仓促,还有一些没有写到(下次再补了)

挂一个板子吧。

(不要问我为什么没有链接,因为luogu的板题还没做,只做了校内OJ的题)

题意:给定一个长度为\(n\)的序列,\(m\)个询问,每个询问的形式为:\(l,r,k\)表示在\([l,r]\)间中的第\(k\)小元素。

数据范围:\((n\leq 10^5,m\leq 10^5,1\leq l\leq r\leq n, 1\leq k\leq r-l+1,1\leq a_i\leq 10^9)\)

code:

#include<bits/stdc++.h>
using namespace std;

#define ll long long
#define rp(i,o,p) for(ll i=o;i<=p;++i)
#define pr(i,o,p) for(ll i=o;i>=p;--i)

const ll MAXN=1e5+5,INF=1e9;

ll n,m,tot;
ll a[MAXN];
ll rts[MAXN];
struct TREE
{
    ll lc,rc,sz;
}tr[MAXN<<2];

void update(ll r)
{
    tr[r].sz=tr[tr[r].lc].sz+tr[tr[r].rc].sz;
}

void ins(ll &rt,ll lst,ll l,ll r,ll val)
{
    rt=++tot;
    tr[rt]=tr[lst];
    if(l==r)
        tr[rt].sz++;
    else
    {
        ll mid=(l+r)>>1;
        if(val<=mid)
            ins(tr[rt].lc,tr[lst].lc,l,mid,val);
        else
            ins(tr[rt].rc,tr[lst].rc,mid+1,r,val);
        update(rt);
    }
}

ll query(ll r1,ll r2,ll l,ll r,ll k)
{
    if(l==r)
        return l;
    ll lc1=tr[r1].lc,lc2=tr[r2].lc;
    ll mid=(l+r)>>1;
    ll tmp=tr[lc2].sz-tr[lc1].sz;
    if(tmp>=k)
        return query(lc1,lc2,l,mid,k);
    return query(tr[r1].rc,tr[r2].rc,mid+1,r,k-tmp);
}

int main()
{
    scanf("%lld%lld",&n,&m);
    rp(i,1,n)
    {
        scanf("%lld",&a[i]);
        ins(rts[i],rts[i-1],1,INF,a[i]);
    }
    rp(i,1,m)
    {
        ll x,y,z;
        scanf("%lld%lld%lld",&x,&y,&z);
        printf("%lld\n",query(rts[x-1],rts[y],1,INF,z));
    }
    return 0;
}

标签:sz,插入,持续,线段,tr,leq,ll
From: https://www.cnblogs.com/wang-holmes/p/17406787.html

相关文章

  • P3919 【模板】可持久化线段树 1(可持久化数组) 题解
    一、题目描述:维护这样的一个长度为$n$的数组,支持以下两种操作$1$:在某个历史版本上修改某一个位置上的值$2$:访问某个历史版本上的某一位置的值每进行一次操作,就会生成一个新的版本(对于操作2,生成的就是一个完全一样的版本)。版本编号即为当前操作......
  • 线段树选记
    1.[TJOI2018]数学计算题目描述小豆现在有一个数\(x\),初始值为\(1\)。小豆有\(Q\)次操作,操作有两种类型:1m:将\(x\)变为\(x\timesm\),并输出\(x\bmodM\)2pos:将\(x\)变为\(x\)除以第\(pos\)次操作所乘的数(保证第\(pos\)次操作一定为类型1,对于每一个类型1......
  • hdu:LCIS(线段树+区间合并)
    ProblemDescriptionGivennintegers.Youhavetwooperations:UAB:replacetheAthnumberbyB.(indexcountingfrom0)QAB:outputthelengthofthelongestconsecutiveincreasingsubsequence(LCIS)in[a,b].InputTinthefirstline,indicatingt......
  • 持续熬夜爆肝,炸裂的OPEN AI 快速开发平台后台管理同步上线啦 ,完全免费聊天主题也即将
    持续几天几夜晚,不眠不休的项目开发,终于完成第一版整OPENAI快速开发平台API和大家见面了,这次包含后台管理,用户开发者入住,和完整的接口文档OPENAI快速开发平台这里进入连接上一篇文章爆肝一周,我开源了ChatGPT中文版接口,官方1:1镜像支持全部官方接口目前支持功能普通对......
  • 小程序优化之旅(四) -- 项目持续化集成与自动化上传代码
    一、前情提要成1.1改造的目的概述在开发小程序的完成项目流程当中,免不了需要上传代码到微信平台,这个处理在以前是只能通过小程序开发者工具界面进行人工手动点击按钮进行;这个过程是十分枯燥并且一定程度上消耗了宝贵的人力资源。后续微信提供了小程序的CI工具,正式进入通过跑......
  • c++打卡练习(28)(还没写对,持续改进中)
    黑洞数流程图:伪代码:源代码:#include<iostream>usingnamespacestd; intmaxof3(int,int,int); intminof3(int,int,int); intmain(){ inti,k; inthun,oct,data,max,min,j; printf("请输入一个三位数\n"); scanf("%d",i); while(k!=EOF){ hun=i/100; ......
  • 线段树合并 学习笔记
    算法两棵动态开点线段树,直接把一棵线段树上的信息合并到另一棵树上。递归合并:如果某个节点两棵都有,合并,然后递归下去。否则直接返回节点。复杂度证明记权值线段树值域范围为\(m\),\(n\)次插入操作。因为动态开点,一次插入会新增\(\log_2m\)个节点,总节点数\(n\ti......
  • 线段树
    线段树--解决区间问题的数据结构,相比于树状数组,更具有普适性;完全二叉树的性质:根节下标为1,,节点为i的节点,左子节点为2*i,右子节点为2*i+1;代表nums中单个元素的节点tree[x]应当在树的最底层,即叶子节点;更大的区间从叶子节点开始向上构成;代表区间【L,R】的节点tree【i】,左子节点tre......
  • C/C++常用接口实现【持续更新】
    目录判断大小端判断大小端一般方法boolIsSmallEndian(){intnum=1;char*p=(char*)&num;if(*p==1){printf("小端\n");returntrue;}returnfalse;}unionboolIsSmallEndian(){unionUn{char......
  • CTF-MISC-内存取证(持续更新)
    1.恶意程序注册表题目来源-2021强网杯 附件为未知文件,用010editor查看  7z文件,修改后缀名 解压出一个镜像文件非预期解:根据题目提示,可以搜索注册表申请命令REGQUERY命令,用010editor查看 可以得到答案正确解法可查看这个大佬(30条消息)第五届强网杯全国网络......