首页 > 其他分享 >可持久化线段树学习笔记

可持久化线段树学习笔记

时间:2023-10-29 19:02:41浏览次数:28  
标签:cnt 持久 int 线段 mid 笔记 ls 节点

可持久化线段树

前置知识:

  • 动态开点线段树

基本介绍

可持久化线段树可以维护多个版本信息。

举个例子:

你需要维护这样的一个长度为 \(N\ (1\le n\le 10^6)\) 的数组,支持如下几种操作

  1. 在某个历史版本上修改某一个位置上的值
  2. 访问某个历史版本上的某一位置的值

每次操作后生成一个新的版本。

(就是板子1)

考虑使用线段树,开多个线段树,每次操作复制一遍,但是这样会爆。

我们发现,线段树上每次操作最多只有 \(log(n)\) 个节点被改变,所以我们每次修改时,对于未被改变的节点,直接指向先前版本的节点。

如上图,我们以 版本 \(x-1\) 为基础,将第 \(8\) 个元素修改,并创建一个新的版本 \(x\) 。对于未被修改的节点 \([1,4],[5,6],[7,7]\) 我们直接指向 版本 \(x-1\) 的对应节点,被修改的节点 \([1,8],[5,8],[7,8],[8,8]\) 需要创建。

可持久化线段树一般为单点修改,区间修改可以用标记永久化(但是我不会)。数组要开到 Max<<5 左右

板子1代码

点击查看代码
#include <iostream>
#define int long long
const int M = 1e6+10;
int n,m,a[M],root[M],cnt,tot;
struct Tree
{
    int ls,rs,num;
}t[M<<5];
void build(int x,int l,int r)
{
    if(l==r){
        t[x].num=a[l];
        return ;
    }
    int mid=(l+r)>>1;
    build(t[x].ls=++cnt,l,mid);
    build(t[x].rs=++cnt,mid+1,r);
    return ;
}
void change(int p,int q,int l,int r,int x,int y,int num)
{
    if(l>=x&&r<=y){
        t[q].num=num;
        return ;
    }
    t[q].ls=t[p].ls,t[q].rs=t[p].rs;
    int mid=(l+r)>>1;
    if(mid >= x) change(t[p].ls,t[q].ls=++cnt,l,mid,x,y,num);
    if(mid < y) change(t[p].rs,t[q].rs=++cnt,mid+1,r,x,y,num);
    return ;
}
int getNum(int q,int l,int r,int x,int y)
{
    if(l>=x&&r<=y)
        return t[q].num;
    int mid=(l+r)>>1;
    if(mid >= x) return getNum(t[q].ls,l,mid,x,y);
    if(mid < y) return getNum(t[q].rs,mid+1,r,x,y);
}
inline int read(){
    int num=0,fl=1;char c=getchar();
    while(c<'0'||c>'9'){
        if(c=='-') fl=-1;
        c=getchar();
    }
    while(c >='0'&&c <='9'){
        num=(num<<3)+(num<<1)+(c^48);
        c=getchar();
    }
    return num*fl;
}
signed main(){
    n=read(),m=read();
    for(int i = 1;i<=n;i++)a[i]=read();
    build(root[0]=++cnt,1,n);
    while(m--){
        int v=read(),mod=read();
        if(mod==1){
            int x=read(),num=read();
            change(root[v],root[++tot]=++cnt,1,n,x,x,num);
        }
        if(mod==2){
            int x=read();
            printf("%lld\n",getNum(root[v],1,n,x,x));
            root[++tot]=root[v];
        }
    }
    return 0;  
}

静态区间 \(k\) 小值

可持久化线段树的第二种经典用法:查询区间的第 \(k\) 小个数。(板子2)

这里似乎就是主席树了(可持久化权值线段树)。

序列中的每个位置作为一个版本,维护当前位置前面(包括当前位置)所有元素的出现次数。(即节点 \([x,y]\) 表示 \(x\le a_i\le y\) 的数量) 。然后就可以通过差分得出 \([l,r]\) 区间内的元素的出现数量。最后考虑在线段树里二分,若左节点的值大于 \(k\) 则向左节点递归,否则将 \(k\) 减去左节点的值向右节点递归。

板子2代码

点击查看代码
#include <iostream>
#include <queue>
#include <vector> 
#define int long long
const int M = 2e5+10;
const int inf = 1e9;
int n,m;
int a[M];
int root[M],cnt,tot;
struct Tree
{
    int ls,rs,cnt;
}t[M<<5];
inline void pushUp(int x){t[x].cnt=t[t[x].ls].cnt+t[t[x].rs].cnt;}
void change(int p,int q,int x,int y,int l,int r)
{
    if(l >= x&&r <= y){
        t[q].cnt=t[p].cnt+1;
        return ;
    }
    t[q].ls=t[p].ls,t[q].rs=t[p].rs;
    int mid=(l+r)>>1;
    if(mid >= x) change(t[p].ls,t[q].ls=++cnt,x,y,l,mid);
    if(mid < y) change(t[p].rs,t[q].rs=++cnt,x,y,mid+1,r);
    pushUp(q);
    return ;
}
void Ans(int l,int r,int p,int q,int k)
{
    if(l==r){
        printf("%lld\n",l);
        return;
    }
    int mid=(l+r)>>1;
    if(k<=t[t[q].ls].cnt-t[t[p].ls].cnt) Ans(l,mid,t[p].ls,t[q].ls,k);
    else  Ans(mid+1,r,t[p].rs,t[q].rs,k-(t[t[q].ls].cnt-t[t[p].ls].cnt));
}
inline int read(){
    int num=0,fl=1;char c=getchar();
    while(c<'0'||c>'9'){
        if(c=='-') fl=-1;
        c=getchar();
    }
    while(c >='0'&&c <='9'){
        num=(num<<3)+(num<<1)+(c^48);
        c=getchar();
    }
    return num*fl;
}
signed main(){
    n=read(),m=read();
    root[0]=++cnt;
    for(int i = 1;i <= n;i++){
        a[i]=read();
        change(root[i-1],root[i]=++cnt,a[i],a[i],-inf,inf);
    }
    while(m--){
        int l=read(),r=read(),k=read();
        Ans(-inf,inf,root[l-1],root[r],k);
    }
    return 0;  
}

一些其他题

跟区间第 \(k\) 小差不多。二分查 \(k\) 小值改成查 \(\dfrac{l+r}{2}\) 就行了。

蜜豆

标签:cnt,持久,int,线段,mid,笔记,ls,节点
From: https://www.cnblogs.com/cnm2k3h/p/17796199.html

相关文章

  • 学习笔记7
    一、知识点总结本章论述了并发编程,介绍了并行计算的概念。指出了并行计算的重要性:比较了顺序算法与并行算法,以及并行性与并发性;解释了线程的原理及其相对于进程的优势;介绍了Pthread中的线程操作,包括线程管理函数,互斥量、条件变量和屏障等线程同步工具;解释了死锁问题,并说明了如何......
  • 《信息安全系统设计与实现》第八次学习笔记
    第四章:并发编程并行计算导论顺序算法与并行算法顺序算法:所有步骤通过单个任务依次执行,每次执行一个步骤,当所有步骤执行完成时,算法结束。并行算法:cobegin-coend代码块来指定独立任务,所有任务都是并行执行的,紧接着cobegin-coend代码块的下一个步骤将只在所有这些任务完成之后执......
  • 2023-2024-1 20211327 信息安全系统设计与实现 学习笔记7
    学习笔记7顺序算法与并行算法线程的原理与优缺点线程管理函数线程同步实践过程顺序算法与并行算法顺序算法(SequentialAlgorithm)原理:顺序算法是一种线性执行的算法,它按照顺序一步一步地解决问题。这意味着每个操作都依赖于前一个操作的结果,只有在前一个操作完成之后才......
  • Unix/Linux系统编程自学笔记-第四章:并发编程
    1、并行计算并行计算并行计算是一种计算方法,通过使用多个执行并行算法的处理器相较串行计算更快地解决问题。现代多核处理器的结构能很好的实现并行计算。计算机的发展未来也是并行计算。顺序算法与并行计算顺序算法一般代码块格式如下,顺序算法的每个代码块可能包含多......
  • Linux shell编程学习笔记16:bash中的关联数组
    上一节我们探讨了普通的数组,即使用数字下标来索引数组中不同的元素的数组,也可以称之为索引数组。相比纯粹的数字,字符串不仅能表明含义,也更便于记忆使用,于是就有了关联数组。一、关联数组概述bash从4.0开始支持关联数组,关联数组可以使用可以使用任意的字符串、或者整数作为下标来......
  • [学习笔记]--信息安全
    信息系统安全属性安全属性属性描述保密性最小授权原则,防暴露,信息加密,物理保密完整性安全协议,校验码,密码校验,数字签名,公证可用性综合保障(IP过滤,业务流程控制,路由选择控制,审计跟踪)不可抵赖性数字签名对称加密与非对称加密对称加密:加密和......
  • 2023/10/29 学习笔记
    学习安装yum源仓库与编译安装Linux中安装软件分三大类:rpm:类似360软件管家红帽公司开发出来的工具编译安装:将源代码编译成可执行文件(二进制包安装)自由度高yum:最后用的还是rpm,它是rpm的升级版本rpm:——查询、安装、卸载查询rpm-q 软件查询h指定软件包是否......
  • python面向对象-学习笔记(三、类方法、实例方法、静态方法)
    方法相关方法的概念描述一个目标的行为动作和函数相似封装了一系列行为动作。比如一个人怎么吃,怎么喝,怎么玩...都可以被调用最主要区别:调用方式方法的划分实例方法:默认第一个参数是一个实例类方法:默认第一个参数是类静态方法:没有默认参数注意划分的依据:方法的第一......
  • python面向对象-学习笔记(四、类相关的补充)
    元类创建类对象的类对象怎么产生的?由类创建出来的。类是不是对象?是所以类对象是不是由另外一个类创建出来的?是,元类创建类对象的另外一种方式#创建类对象的另外一种方式defrun(self):print("run",self)dog=type("Dog",(),{"count":1,"run":run})prin......
  • python面向对象-学习笔记(五、属性相关的补充)
    私有化属性注意python并没有真正支持私有化,但是可以使用下划线完成伪私有的效果类属性(方法)和实例属性(方法)遵循相同的规则公有属性a在类的内部可以访问在子类的内部可以访问在模块其他地方类的属性可以访问子类的属性可以访问类的实例的属性可以访问子类的......