学了一年 OI 才看懂这句话:

\(\log n\) 是以什么为底的?

其实没什么区别

因为我们自动忽略常数,因此 \(\log_{a}n=\frac{\log_{x}n}{\log_{x}a}=\log_{x}n\)

可持久化数组

可持久化数组是基于可持久化线段树的一种数据结构. 它可以支持如下操作:

  • 单点修改
  • 查询历史版本
  • 在历史版本上修改

考虑到,为了实现这个功能,我们可以给每个历史版本都复制一份副本,但是这样的复杂度是 \(O(nt)\) 的

考虑优化空间复杂度:现在把整个数组放在线段树上,令数组为线段树的全部叶节点,因为我们每次修改节点最多只会带来 \(\log n\) 的改变,因此我们考虑在原树上仅仅改变这些点. 用动态开点思想可以很轻松地做到这一点.

比较水,直接放代码了

namespace HIST_Stree{
const int N=1000000;
int root[N];
#define mid(l,r) mid=((l)+(r))/2
struct tree{
int tol,tor;
int w;
}t[30*N];
#define tol t[id].tol
#define tor t[id].tor
int cnt=0;
int newnode(){
t[++cnt]={};
return cnt;
}
int clone(int node){
t[++cnt]=t[node];
return cnt;
}
void build(int &id,int l,int r){
id=newnode();
if(l==r){
t[id].w=a[l];
return;
}
int mid(l,r);
build(tol,l,mid);
build(tor,mid+1,r);
}
void update(int &id,int l,int r,int pos,int val){
id=clone(id);
if(l==r){
t[id].w=val;
return;
}
int mid(l,r);
if(pos<=mid) update(tol,l,mid,pos,val);
else update(tor,mid+1,r,pos,val);
}
int ask(int id,int l,int r,int pos){
if(l==r){
return t[id].w;
}
else{
int mid(l,r);
if(pos<=mid) return ask(tol,l,mid,pos);
else return ask(tor,mid+1,r,pos);
}
}
} if(op==1){
scanf("%d",&val);
update(root[i]=root[t],1,n,pos,val);
}
else{
printf("%d\n",ask(root[t],1,n,pos));
root[i]=root[t];
}

可持久化线段树

可持久化线段树最经典的应用即为求区间第 \(k\) 小

先不考虑可持久化,考虑开一颗权值线段树,那么我们按照下述方法即可求出区间 \([1,r]\) 内的第 \(k\) 小:

先将原数组 \(a\) 排序离散化为 \(b\),将 \(a\) 中的元素依次插入权值线段树内. 每个节点插在位于 \(b\) 数组的位置上

为了方便统计答案,我们规定:插入节点时,途径节点的值加一

比如下列数组:

a: 1 5 2 6 3 7 4

排序后为

b: 1 2 3 4 5 6 7

图源

首先插入 \(a[1]=1\),其路径如下:

其次插入 \(a[2]=5\):

以此类推

最终会变成这样:

可以发现:因为我们开的是权值线段树,因此总是满足左区间小于右区间,因此要求 \([1,r]\) 的第 \(k\) 小,我们只需要考虑想二叉搜索树一样递归减掉 \(rank\) 即可.

那么对于求区间 \([l,r]\) 的问题怎么办呢?其实可以基于权值线段树有可减性的思想,直接用版本 \(r\) 减去版本 \(l-1\),剩下的就是在 \([l,r]\) 内的修改了.

实际实现的时候并不需要真的减出一颗树来,可以通过作差来做.

namespace HIST_Stree{
#define mid(x,y) mid=((x)+(y))/2
const int N=200001;
int root[N],cnt=0;
struct tree{
int tol,tor;
int sum;
}t[N*32];
#define tol(id) t[id].tol
#define tor(id) t[id].tor
int newnode(){
t[++cnt]={};
return cnt;
}
int clone(int node){
t[++cnt]=t[node];
return cnt;
}
void build(int &id,int l,int r){
id=newnode();
if(l==r){
return;
}
int mid(l,r);
build(tol(id),l,mid);
build(tor(id),mid+1,r);
}
void change(int &id,int l,int r,int pos){
id=clone(id);
t[id].sum++;
if(l==r) return;
int mid(l,r);
if(pos<=mid) change(tol(id),l,mid,pos);
else change(tor(id),mid+1,r,pos);
}
int ask(int x,int y,int l,int r,int k){
if(l==r) return l;
int mid(l,r);
if(t[t[y].tol].sum-t[t[x].tol].sum>=k){
return ask(t[x].tol,t[y].tol,l,mid,k);
}
else{
return ask(t[x].tor,t[y].tor,mid+1,r,k-(t[t[y].tol].sum-t[t[x].tol].sum));
}
}
}
using namespace HIST_Stree; sort(b+1,b+n+1);
int l=unique(b+1,b+n+1)-b-1;
build(root[0],1,l);
for(int i=1;i<=n;++i){
change(root[i]=root[i-1],1,l,lower_bound(b+1,b+l+1,a[i])-b);
}
while(m--){
int L,R,K;
scanf("%d %d %d",&L,&R,&K);
printf("%d\n",b[ask(root[L-1],root[R],1,l,K)]);
}

可持久化线段树小结

根据上述问题的解法可以发现,可持久化值域线段树因为其出色的可加减性,因而经常被当做前缀和用.

什么时候才会让可持久化线段树充当前缀和的角色?通常是那些需要对不同版本求前缀和的问题

但实际上并没有哪个题会容易让你看出来怎么构建历史版本(或者根本看不出来可以构建历史版本),这才是可持久化线段树题最难的地方.

可持久化线段树并不全是值域线段树,但是其他的并不常见,多用来构成其他可持久化数据结构. 如可持久化数组,可持久化并查集等.

在维护前缀和时,通常我们需要维护一个求答案的式子,比如:

推知答案 \(ans=w_{x}-w_{y}\)(举例)

注意到这个答案式里面有两个参数,那么只需要传两个历史版本 \((a,b)\),在可持久化线段树里先维护每个节点的 \(f(x)\),然后再在求解过程中维护两个版本的 \(f_{a}(x)-f_{b}(y)\)(这是由你推的式子决定的),再同时跳到各自版本的下一个节点.

多维函数也是一样,比如 P2633,它的答案式为 \(f(x,y)=w_{x}+w_{x}-w_{lca(x,y)}-w_{fa_lca(x,y)}\),因此我们传四棵树(\(root_{x},root_{y},root_{lac},root_{fa_{lca}}\))来统计答案,跳的时候四棵树一起跳.

这些题都是类似的,实际情况实际处理即可.

[OI] 可持久化数据结构的更多相关文章

  1. 可持久化数据结构QwQ(持续更新中)

    可持久化留下的迹象 我们俯身欣赏 ——<膜你抄>By Menci&24OI Micardi最近在学可持久化的东西,可持久化线段树.可持久化并查集.可持久化01Trie......等 ...

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

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

  3. 可持久化数据结构(平衡树、trie树、线段树) 总结

    然而好像没有平衡树 还是题解包: T1:森林 树上主席树+启发式合并. 然而好像知道标签就没啥了.在启发式合并时可以顺手求lca 然而这题好像可以时间换空间(回收空间) T2:影魔 难点在于考虑贡献的 ...

  4. 常用数据结构的功能及复杂度总结(OI)

    不定长数组 维护一个序列 在末尾插入/删除均摊O(1) 任意位置插入O(n) 指定位置查询/修改O(1) 空间O(n) 链表 维护一个序列 定位到第i个位置O(n) 在任意位置(已定位到该位置)插入/ ...

  5. 模板 - 数据结构 - 可持久化无旋Treap/PersistentFHQTreap

    有可能当树中有键值相同的节点时,貌似是要对Split和Merge均进行复制的,本人实测:只在Split的时候复制得到了一个WA,但只在Merge的时候复制还是AC,可能是恰好又躲过去了.有人说假如确保 ...

  6. OI知识点|NOIP考点|省选考点|教程与学习笔记合集

    点亮技能树行动-- 本篇blog按照分类将网上写的OI知识点归纳了一下,然后会附上蒟蒻我的学习笔记或者是我认为写的不错的专题博客qwqwqwq(好吧,其实已经咕咕咕了...) 基础算法 贪心 枚举 分 ...

  7. OI省选算法汇总

    copy from hzwer @http://hzwer.com/1234.html 侵删 1.1 基本数据结构 1. 数组 2. 链表,双向链表 3. 队列,单调队列,双端队列 4. 栈,单调栈 ...

  8. 【BZOJ-3673&3674】可持久化并查集 可持久化线段树 + 并查集

    3673: 可持久化并查集 by zky Time Limit: 5 Sec  Memory Limit: 128 MBSubmit: 1878  Solved: 846[Submit][Status ...

  9. UVALive 6145 Version Controlled IDE(可持久化treap、rope)

    题目链接:https://icpcarchive.ecs.baylor.edu/index.php?option=com_onlinejudge&Itemid=8&page=show_ ...

  10. 可持久化trie 学习总结

    QAQ 以前一直觉得可持久化trie很难,今天强行写了一发觉得还是蛮简单的嘛 自己的模板是自己手写的,写了几道题目并没有出过错误 THUSC的第二题的解法五貌似就是可持久化trie,时间复杂度O(60 ...

随机推荐

  1. 使用 useLazyFetch 进行异步数据获取

    title: 使用 useLazyFetch 进行异步数据获取 date: 2024/7/20 updated: 2024/7/20 author: cmdragon excerpt: 摘要:&quo ...

  2. Kubernetes 存储概念之Volumes介绍

    Volumes 默认情况下容器中的磁盘文件是非持久化的,对于运行在容器中的应用来说面临两个问题,第一:当容器挂掉,K8S重启它时,文件将会丢失:第二:当Pod中同时运行多个容器,容器之间需要共享文件时 ...

  3. CSP2023-J/S 游记

    本人 初二 \(\texttt{HA}\) CSP2023 成绩: CSP-J 第一轮:\(86.5\) CSP-S 第一轮:\(41.5\) CSP-J 第二轮:\(100+100+100+0=30 ...

  4. 记hashmap

    hashmap是map接口的一个实现类,在同步的情况下hashmap的性能是比较好的 hashmap就是一个kv键值对的集合,将数值散列均匀的存储在哈希表中.插入方法为map.put(k,v),读取方 ...

  5. SEO自动外链工具的功效以及使用心得

    SEO外链发布工具原理 1.自动SEO外链工具原理:就是把您的网址提交大站长工具类似的网站上面进行搜索,然后就会在上面留下痕迹自动生成以网址为标题的静态页面. 2.自动SEO外链发布效果:我们就是利用 ...

  6. SpringBoot 配置统一API对象返回

    1.前言 在实际项目开发中,为了便于前端进行响应处理,需要统一返回类格式.特别是在有多个后端开发人员参与的情况下,如果不规范返回类,每个人按照个人习惯返回数据,前端将面临各式各样的返回数据,难以统一处 ...

  7. (续)使用MindSpore_hub 进行 加载模型用于推理或迁移学习

    接前文: https://www.cnblogs.com/devilmaycry812839668/p/15005959.html ================================== ...

  8. Spring Boot Admin对Springboot服务进行监控

    1.背景 大纲 Spring Boot Admin 是一个管理和监控Spring Boot 应用程序的开源软件.每个应用都认为是一个客户端,通过HTTP或者使用 Eureka注册到admin serv ...

  9. .NET 屏幕录制

    窗口/屏幕截图适用于截图.批注等工具场景,时时获取窗口/屏幕图像数据流呢,下面讲下视频会议共享桌面.远程桌面这些场景是如何实现画面录制的. 常见的屏幕画面时时采集方案,主要有GDI.WGC.DXGI. ...

  10. LLM大模型部署实战指南:Ollama简化流程,OpenLLM灵活部署,LocalAI本地优化,Dify赋能应用开发

    LLM大模型部署实战指南:Ollama简化流程,OpenLLM灵活部署,LocalAI本地优化,Dify赋能应用开发 1. Ollama 部署的本地模型() Ollama 是一个开源框架,专为在本地机 ...