ACM学习笔记:可持久化线段树
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学习笔记:可持久化线段树的更多相关文章
- [学习笔记] 可持久化线段树&主席树
众所周知,线段树是一个非常好用也好写的数据结构, 因此,我们今天的前置技能:线段树. 然而,可持久化到底是什么东西? 别急,我们一步一步来... step 1 首先,一道简化的模型: 给定一个长度为\ ...
- [学习笔记]可持久化数据结构——数组、并查集、平衡树、Trie树
可持久化:支持查询历史版本和在历史版本上修改 可持久化数组 主席树做即可. [模板]可持久化数组(可持久化线段树/平衡树) 可持久化并查集 可持久化并查集 主席树做即可. 要按秩合并.(路径压缩每次建 ...
- HDU 5820 (可持久化线段树)
Problem Lights (HDU 5820) 题目大意 在一个大小为50000*50000的矩形中,有n个路灯.(n<=500000) 询问是否每一对路灯之间存在一条道路,使得长度为|x1 ...
- HDU 4348 To the moon 可持久化线段树,有时间戳的区间更新,区间求和
To the moonTime Limit: 20 Sec Memory Limit: 256 MB 题目连接 http://acm.hust.edu.cn/vjudge/contest/view.a ...
- HDU 5919 Sequence II(可持久化线段树)
[题目链接]http://acm.hdu.edu.cn/showproblem.php?pid=5919 [题目大意] 给出一个数列,每次查询数列中,区间非重元素的下标的中位数.查询操作强制在线. [ ...
- 计蒜客 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 ...
- bzoj 3673&3674 可持久化并查集&加强版(可持久化线段树+启发式合并)
CCZ在2015年8月25日也就是初三暑假要结束的时候就已经能切这种题了%%% 学习了另一种启发式合并的方法,按秩合并,也就是按树的深度合并,实际上是和按树的大小一个道理,但是感觉(至少在这题上)更好 ...
- HDU 4417.Super Mario-可持久化线段树(无修改区间小于等于H的数的个数)
Super Mario Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total ...
- 洛谷——P3919 【模板】可持久化数组(可持久化线段树/平衡树)
P3919 [模板]可持久化数组(可持久化线段树/平衡树) 题目背景 UPDATE : 最后一个点时间空间已经放大 标题即题意 有了可持久化数组,便可以实现很多衍生的可持久化功能(例如:可持久化并查集 ...
随机推荐
- getopt模块的学习
在运行程序时,可能需要根据不同的条件,输入不同的命令行选项来实现不同的功能.目前有短选项和长选项两种格式.短选项格式为"-"加上单个字母选项:长选项为"--"加 ...
- MySQL 中的转义字符`
` 是 MySQL 的转义符,用来避免列名或者表名和 mysql 本身的关键字冲突. 所有的数据库都有类似的设置,不过mysql用的是`而已.通常用来说明其中的内容是数据库名.表名.字段名,不是关键字 ...
- IDA PRO:庆祝成立创新 30 周年
今天,IDA 已经三十岁了.为了纪念周年纪念,我们将描述史诗旅程的开始和主要里程碑. 背景 在 1990 年代初期,DOS 是最流行的 PC 操作系统,主要是 8086,偶尔有 80286(80386 ...
- centos安装报错:license information (license not accepted)
前言:在最近部署的centos系统发现个问题 出现报错:安装配置完成后,重启虚拟机出现license information (license not accepted) 截图: 解决方案: 在界 ...
- WPF技巧:命中测试在视觉树中的使用
我们有时候又需求从当前视觉树中找一些东西,比如鼠标按下的时候,看看鼠标下的元素都有什么.又比如某块区域下有哪些元素?某个坐标点下有哪些元素? 这些需求在使用 命中测试的时候,可以非常方便和快速的去找到 ...
- python 17篇 unittest单元测试框架
单元测试:开发程序的人自己测试自己的代码 unittest自动化测试框架 1.单元测试 import unittest def add(a,b): return a+b # 在运行时不要用run un ...
- 一文搞懂一致性hash的原理和实现
在 go-zero 的分布式缓存系统分享里,Kevin 重点讲到过一致性hash的原理和分布式缓存中的实践.本文来详细讲讲一致性hash的原理和在 go-zero 中的实现. 以存储为例,在整个微服务 ...
- C语言:带参数的宏与函数的区别
带参数的宏和函数很相似,但有本质上的区别:宏展开仅仅是字符串的替换,不会对表达式进行计算:宏在编译之前就被处理掉了,它没有机会参与编译,也不会占用内存.而函数是一段可以重复使用的代码,会被编译,会给它 ...
- 团队开发day10
项目整合成功,测试功能基本达到,目标完成!
- 家庭账本开发day08
对查询到额数据进行相关的操作,删除.对删除按钮绑定事件 点击后发送ajax请求到servlet,删除相关的数据后,返回flag到前端 若后台删除成功,则前台进行相应的.close():输出点击行的数据 ...