title : 可持久化线段树
date : 2021-8-18
tags : 数据结构,ACM

可持久化线段树

可以用来解决线段树存储历史状态的问题。

我们在进行单点修改后,线段树只有logn个(一条链)的节点被修改,我们可以让修改后的树与修改前的树共享节点,节省时间和空间。

在学习之前,我们先引入三个前置知识:离散化、动态开点,权值线段树。

离散化

对于较大的数据范围,只要将关键点记录下来,记录下rank,就能把数据缩小到可以接受的范围,以便建立线段树或其他数据结构来解决问题。

具体步骤

(1)将所有端点加入辅助数组; (2)按坐标从小到大排序; (3)去重; (4)数据离散化,用hs数组记录端点的排名而非具体数字

vector<int>vt;
for(int i=1;i<=n;i++){
   cin>>a[i];
   vt.push(a[i]); //加入辅助容器
}
sort(vt.begin(),vt.end());//排序
vt.erase(unique(vt.begin(),vt.end()),vt.end()); //去重
for(int i=0;i<vt.size();i++){
   hs[vt[i]]=++tot; //存储为rank
}

动态开点

动态开点线段树可以避免离散化。

如果权值线段树的值域较大,离散化比较麻烦,可以用动态开点的技巧。

省略了建树的步骤,而是在具体操作中加入结点。

权值线段树

线段树的叶子节点保存的是当前值的个数。

每个节点保存区间左右端点以及所在区间节点的个数。

由于值域范围通常较大,一般会配合离散化或动态开点等策略优化空间。

应用

查找一个区间的第k大的值

查询某个数的排名

查询整个数组的排序

查询前驱和后继

单点修改
void update(int node,int start,int end,int pos){
   if(start==end) tr[node]++;
   else{
       int mid=start+end>>1;
       if(pos<=mid) update(node<<1,start,mid,pos);
       else update(node<<1|1,mid+1,end,pos);
  }
}//tr[i]表示值为i的元素个数,pos是要查找的位置
查询区间中的数出现次数
int query(int node,int start,int end,int ql,int qr){
   if(start==ql&&end==qr) return tr[node];
   int mid=start+end>>1;
   if(qr<=mid) return query(node<<1,start,mid,ql,qr);
   else if(ql>mid) return query(node<<1|1,mid+1,end,ql,qr);
   else return query(node<<1,start,mid,ql,qr)+query(node<<1|1,mid+1,end,ql,qr);
}//对单点查询同样适用
查询所有数的第k大值
int kth(int node,int start,int end,int k){
   if(start==end) return start;
   int mid=start+end>>1;
   int s1=tr[node<<1],s2=tree[node<<1|1];
   if(k<=s2) return kth(node<<2|1,mid+1,end,k);
   else return kth(node<<1,start,mid,k-s2);
} //注意是第k大,从右边开始减,如果是第k小就减去左边
查询前驱(后继同)
int findpre(int node,int start,int end){ //找这个区间目前最大的
   if(start==end) return start; //找到直接返回
   int mid=start+end>>1;
   if(t[node<<1|1]) return findpre(node<<1|1,mid+1,end);
   return findpre(node<<1,start,mid);
}

int pre(int node,int l,int r,int pos){ //求pos的前驱
   if(r<pos){ //在最右边
       if(t[node]) return findpre(node,l,r);
       return 0;
  }
   int mid=l+r>>1,res;
   if(mid+1<pos&&t[node<<1|1]&& (res=pre(node<<1|1,mid+1,r,pos))) return res; //在右区间寻找
   return pre(node<<1,l,mid,pos);  //在左区间寻找
}

可持久化线段树

复杂度分析

建树O(nlogn)

询问为O(logn)

空间复杂度O(nlognlogn)。

核心思想

以前缀和形式建立,基于动态开点的存储形式。

两棵线段树之间是可减的(每一个节点对应相减)。

存储

hjt[0]充当NULL,从1开始储存根节点

struct Node{
   int lc,rc,sum; //左儿子右儿子和值sum
}hjt[maxn*32] //空间一般开到32即可
int cnt,root[maxn];//内存池计数器和根节点编号
插入

并不改变原来的树,而是新开根节点,并向下开辟

void insert(inr cur,int pre.int pos,int l,int r){
   if(l==r){ //找到这个点,当前版本点值+1
       t[cur].v=t[pre].v+1;
       return;
  }
   int mid=l+r>>1;
   if(pos<=mid){
       t[cur].lc=++e;
       t[cur].rc=t[pre].rc;
       insert(t[cur].lc,t[pre].lc,pos,l,mid);
  }else{
       t[cur].rc=++e;
       t[cur].lc=t[pre].lc;
       insert(t[cur].rc,t[pre].rc,pos,mid+1,r);
  }
   t[cur].v=t[t[cur].lc].v+t[t[cur].rc].v; //pushup
}
询问操作

本质上与权值线段树相同,只需要在区间上作差。

int query(int l,int r,int x,int y,int k){
if (l == r){
return l;
  }
int mid=(l+r)/2;
int sum=tr[tr[y].l].sum-tr[tr[x].l].sum;
if(sum >= k){
return query(l,mid,tr[x].l,tr[y].l,k);
}
else{
       return query(mid+1,r,tr[x].r,tr[y].r,k-sum);
}
}
区间第K小

luoguP3834 【模板】可持久化线段树 2(主席树)

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
const int maxn = 2e5 + 7;

int n, m, cnt, rt[maxn], a[maxn], x, y, k;
//a[i]是原始的序列,rt[i]是第i版本的主席树

struct node
{
int l, r, sum; //树的左端、右端和元素个数
} tr[maxn * 32];

vector<int> vt; //辅助容器

void read(int &data)  //快读优化
{
int x = 0, f = 1;
char ch = getchar();
while (ch < '0' || ch > '9')
{
if (ch == '-')
{
f = f * -1;
}
ch = getchar();
}
while (ch >= '0' && ch <= '9')
{
x = x * 10 + ch - '0';
ch = getchar();
}
data = x * f;
}

int getid(int x)  //返回每个元素的rank
{
return lower_bound(vt.begin(), vt.end(), x) - vt.begin() + 1;
}

void insert(int l, int r, int &x, int y, int pos)
{ //每次修改或插入,新建一个根节点,并向下递归新建节点
tr[++cnt] = tr[y];
tr[cnt].sum++;  //区域元素数量+1
x = cnt; //将原来的树地址指向当前树
if (l == r)
{ //已经是叶子了,返回
return;
}
int mid = (l + r) / 2;
if (mid >= pos)
{  //向左子树插入
insert(l, mid, tr[x].l, tr[y].l, pos);
}
else
{  //向右子树插入
insert(mid + 1, r, tr[x].r, tr[y].r, pos);  
}
}

int query(int l, int r, int x, int y, int k)
{ //l,r表示当前区间,x,y表示旧树和新树,k要在该区间找k小
if (l == r)
{
return l;    //找到则直接返回第k小的值l
}
int mid = (l + r) / 2;
int sum = tr[tr[y].l].sum - tr[tr[x].l].sum;
//左子树相减,其差值为两个版本之间左边相差多少个数
if (sum >= k)
{  //结果大于等于k,询问左子树
return query(l, mid, tr[x].l, tr[y].l, k);
}
else
{  //否则找右子树第k-sum小的数
return query(mid + 1, r, tr[x].r, tr[y].r, k - sum);
}
}

signed main()
{
read(n);
read(m);
for (int i = 1; i <= n; i++)
{
read(a[i]);
vt.push_back(a[i]); //辅助容器用于离散化
}
sort(vt.begin(), vt.end()); //排序
vt.erase(unique(vt.begin(), vt.end()), vt.end()); //去重
for (int i = 1; i <= n; i++)
{  //每次插入都从根节点开始建立新树(形成一条链)
insert(1, n, rt[i], rt[i - 1], getid(a[i]));
}

for (int i = 1; i <= m; i++)
{//第y棵数和第x-1棵数做差,就能查出[x,y]区间第k小值
read(x);
read(y);
read(k);
printf("%d\n", vt[query(1, n, rt[x - 1], rt[y], k) - 1]);

}
return 0;
}

参考资料

https://www.cnblogs.com/young-children/p/11787490.html

https://www.cnblogs.com/young-children/p/11787493.html

https://blog.csdn.net/ModestCoder_/article/details/90107874

https://blog.csdn.net/a1351937368/article/details/78884465

ACM学习笔记:可持久化线段树的更多相关文章

  1. [学习笔记] 可持久化线段树&主席树

    众所周知,线段树是一个非常好用也好写的数据结构, 因此,我们今天的前置技能:线段树. 然而,可持久化到底是什么东西? 别急,我们一步一步来... step 1 首先,一道简化的模型: 给定一个长度为\ ...

  2. [学习笔记]可持久化数据结构——数组、并查集、平衡树、Trie树

    可持久化:支持查询历史版本和在历史版本上修改 可持久化数组 主席树做即可. [模板]可持久化数组(可持久化线段树/平衡树) 可持久化并查集 可持久化并查集 主席树做即可. 要按秩合并.(路径压缩每次建 ...

  3. HDU 5820 (可持久化线段树)

    Problem Lights (HDU 5820) 题目大意 在一个大小为50000*50000的矩形中,有n个路灯.(n<=500000) 询问是否每一对路灯之间存在一条道路,使得长度为|x1 ...

  4. HDU 4348 To the moon 可持久化线段树,有时间戳的区间更新,区间求和

    To the moonTime Limit: 20 Sec Memory Limit: 256 MB 题目连接 http://acm.hust.edu.cn/vjudge/contest/view.a ...

  5. HDU 5919 Sequence II(可持久化线段树)

    [题目链接]http://acm.hdu.edu.cn/showproblem.php?pid=5919 [题目大意] 给出一个数列,每次查询数列中,区间非重元素的下标的中位数.查询操作强制在线. [ ...

  6. 计蒜客 38229.Distance on the tree-1.树链剖分(边权)+可持久化线段树(区间小于等于k的数的个数)+离散化+离线处理 or 2.树上第k大(主席树)+二分+离散化+在线查询 (The Preliminary Contest for ICPC China Nanchang National Invitational 南昌邀请赛网络赛)

    Distance on the tree DSM(Data Structure Master) once learned about tree when he was preparing for NO ...

  7. bzoj 3673&3674 可持久化并查集&加强版(可持久化线段树+启发式合并)

    CCZ在2015年8月25日也就是初三暑假要结束的时候就已经能切这种题了%%% 学习了另一种启发式合并的方法,按秩合并,也就是按树的深度合并,实际上是和按树的大小一个道理,但是感觉(至少在这题上)更好 ...

  8. HDU 4417.Super Mario-可持久化线段树(无修改区间小于等于H的数的个数)

    Super Mario Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total ...

  9. 洛谷——P3919 【模板】可持久化数组(可持久化线段树/平衡树)

    P3919 [模板]可持久化数组(可持久化线段树/平衡树) 题目背景 UPDATE : 最后一个点时间空间已经放大 标题即题意 有了可持久化数组,便可以实现很多衍生的可持久化功能(例如:可持久化并查集 ...

随机推荐

  1. mysql 去重的两种方式

    1.distinct一般用于获取不重复字段的条数 使用原则: 1)distinct必须放在要查询字段的开头,不能放在查询字段的中间或者后面 select distinct name from user ...

  2. linux安装subversion

    原文: https://www.cnblogs.com/liuxianan/p/linux_install_svn_server.html 安装 使用yum安装非常简单: yum install su ...

  3. 排序---python版

    冒泡排序: 比较相邻的元素.如果第一个比第二个大,就交换它们两个: 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对,这样在最后的元素应该会是最大的数: 针对所有的元素重复以上的步骤,除了最 ...

  4. idea本地调式tomcat源码

    前言 上篇文章中一直没搞定的tomcat源码调试终于搞明白了,p神的代码审计星球里竟然有,真的好b( ̄▽ ̄)d ,写一下过程,还有p神没提到的小坑 准备阶段 1.去官网下东西:https://tomc ...

  5. ACM金牌选手算法讲解《线性表》

    哈喽,大家好,我是编程熊,双非逆袭选手,字节跳动.旷视科技前员工,ACM亚洲区域赛金牌,保研985研究生,分享算法与数据结构.计算机学习经验,帮助大家进大厂~ 公众号:『编程熊』 文章首发于: ACM ...

  6. CTF-wtc_rsa_bbq-writeup

    wtc_rsa_bbq 题目信息: 附件: cry200 解题思路: 1.观察cry200文件,发现该文件是一个二进制文件,用二进制模式查看,发现开头为50 4B 03 04,判断该文件是一个zip文 ...

  7. Pandas高级教程之:自定义选项

    目录 简介 常用选项 get/set 选项 经常使用的选项 最大展示行数 超出数据展示 最大列的宽度 显示精度 零转换的门槛 列头的对齐方向 简介 pandas有一个option系统可以控制panda ...

  8. c++中的基本IO

    引言 c++不直接处理输入和输出,而是通过标准库中的类型处理IO.IO的设备可以是文件.控制台.string.c++主要定义了三种IO类型,分别被包含在iostream.fstream.sstream ...

  9. Linux安装Tomcat-Nginx-FastDFS-Redis-Solr-集群——【第九集-补充-热部署项目到tomcat中,但是数据库配置文件错误,中途停止部署,导致执行shutdow.sh报错异常: Could not contact localhost:8005. Tomcat may not be running error while shutting down】

    1,经过千辛万苦的尝试和百度,终于一个博客:http://stackmirror.caup.cn/page/skxugjqj0ldc关于catalina.sh文件的执行引起了我的注意: 2,我执行ca ...

  10. JAVA基础(代码)练习题61~90

    JAVA基础 61.设计一个方法打印数组{'a','r','g','s','e','r'}中下标为1和3的的元素 package Homework_90; /** * 设计一个方法打印数组{'a',' ...