【用学校抄作业带你走进可持久化线段树(主席树)】可持久化线段树概念+全套模板+例题入门:[福利]可持久化线段树)

我似乎很少写这种算法博客

  • 可持久化线段树概念
    • 概念介绍(类比帮助理解)
    • 简单分析一下时间和空间复杂度(内容池)
  • 模板
    • 结构体变量
    • 建树模板
    • 单点修改模板
    • 单点查询模板
    • 区间修改模板(pushup)
    • 区间修改模板(比较特别)
    • 区间查询模板
  • 入门题:可持久化线段树
    • 题目
    • 简单题解
    • 代码实现

我以这种字体提醒大家是类比概念的理解

可持久化线段树概念

概念介绍(类比帮助理解)

概念:可持久化线段树也叫函数式线段树,它的主体是线段树,准确的说,是很多棵线段树。线段树表示的区间,是输入数据的整个权值域,如果值域空间太大,要先离散化可能出现的元素值,压缩值域空间。
线段树存储的信息是权值,维护不同区间的权值分布情况。
对于有n个元素的序列S,构造出n棵线段树T[1…n],每棵树Ti储存序列的每一个前缀S[1…i]中每类数的出现个数,即权值分布情况。
陈立杰、Seter、Fotile等人称这种以值域作为区间长度的线段树为权值线段树。因为每棵线段树Ti维护的是前缀S[1…i]中每类数的出现个数。

一句话概括:每次修改线段树上的值都新建一棵树,保证原来的线段树未被覆盖


给几张图片帮助认识
在这里插入图片描述在这里插入图片描述
对于可持久化线段树的操作,保证每一次的线段树都不要被覆盖
可能会想到每一次都copy一棵完整的新树,然后再新树上进行更改
空间会变成(nm)(nm)(nm),显然是不允许的

其实我们发现每一次都最多只修改log(n)log(n)log(n)个节点(走到了叶子节点)
其余的节点没经过的都跟原来的线段树状态是一样的,
那么我们就没有必要去新建一些多余的节点储存同样的内容
所以可持久化线段树的修改每一次都只新建需要更改的新点,不动的就直接赋成原来的状态
这样就保证了每一个状态下的线段树都被保存下来了,且未对其发生更改
在这里插入图片描述


我们可以类比抄作业在这里插入图片描述
A同学做完了一份作业(树的最初始状态) B同学就开始抄A,发现这其中有几处错误, 那么他就在这几个题目上面赶紧写下自己的正确答案(新建线段树), 然后其它一样的地方就直接链接A同学的答案(先不急着把答案写下来,只用链接地址不变,在后面要用到的时候就直接找到A的答案地址抄就可以了)

假设这个时候C又开始抄B的,又发现B有一些错误, 他就悄悄更改这几处的答案(新建点), 与B一样的就链接地址(虽然链接的是B的地址,但是B链接的是A的地址,所以就相当于C直接链接了A的地址) 以此类推。。。

可持久化就意味着老师检查作业(查询之时)能找到每一位同学各自的答案,并且每位同学都很不要脸
在这里插入图片描述发现了别人的错误都不告诉别人,自己偷偷摸摸的改,生成了自己新的一份答案

不然你想如果A发现自己有个错误,改了的话,所有的同学都要更改那一道题,所以要按住A不让他更改自己的答案,保证后面的答案顺利传下去

我jio得这个类比非常贴近生活实际,浅显易懂啊!!相信很多亲故都已经明白了吧!!


简单分析一下时间和空间复杂度(内容池)

因为是新建的点,并未对以前的先单数进行深度增加或多几个叶子结点
查询和更改都应该是与线段树一样的时间复杂度O(logN)O(logN)O(logN)
m次操作就是O(MlogN)O(MlogN)O(MlogN)是肯定可以接受的


唯一蜜汁迷人的就是这个空间问题,
建初始树的空间与普通线段树是不一样的,
因为普通线段树是num<<1num<<1num<<1num<<1∣1num<<1|1num<<11,叶子结点也会生成多的空儿子
所以空间会变成4N4N4N,而主席树就不一样了,用了内存池再加上写法的原因
叶子结点是不会多生成空儿子的,空间自然就是2N2N2N,尽管本蒟蒻还是按着4N4N4N在开
有m次操作,就有MlogNMlogNMlogN个新点,就会多这么多空间
线段树总空间也就是(2N+MlogN)(2N+MlogN)(2N+MlogN)

不建议大家卡着点开,可以尽可能的多开一些,保证不MLE就ok

模板

学习任何算法都有一定的模板,刚开始多半都是靠背,打多了过后就自然而然就理解了
在这里插入图片描述没办法在这里插入图片描述
接下来的多数模板我们以找最大值为例,对于不同的情况适当更改部分代码即可

结构体变量

#define MAXN 1000005
struct node {int l, r, Max;
//依情况而决定里面的变量是Max,Min,Sum...
}tree[MAXN << 2];

建树模板

我都是写的动态建树,挺不错的,推荐,嘻嘻
这里要注意必须把每一层的节点编号提前存下来,
不然回溯的时候cnt早已多加了很多,这样我们就对不上了
作业乱了,名字跟真实试卷会对不上

int build ( int l, int r ) {int t = ++ cnt;if ( l == r ) {tree[t].l = tree[t].r = 0;tree[t].Max = a[l];return t;}int mid = ( l + r ) >> 1;tree[t].l = build ( l, mid );tree[t].r = build ( mid + 1, r );tree[t].Max = max ( tree[tree[t].l].Max, tree[tree[t].r].Max );return t;
}

单点修改模板

把p这个点修改为v发现这份作业的p题错了,偷偷摸摸不告诉别人自己改

int update ( int num, int p, int v, int l, int r ) {int t = ++ cnt;if ( l == r ) {tree[t].l = tree[t].r = 0;tree[t].Max = v;return t;}int mid = ( l + r ) >> 1;if ( p <= mid ) {tree[t].l = update ( tree[num].l, p, v, l ,mid );tree[t].r = tree[num].r;}else {tree[t].r = update ( tree[num].r, p, v, mid + 1, r );tree[t].l = tree[num].l;}tree[t].Max = max ( tree[tree[t].l].Max, tree[tree[t].r].Max );return t;
}

单点查询模板

假设我们要找p的值老师开始检查作业,抽查到我们的p题答案,马上找链接的答案

int query ( int p, int num, int l, int r ) {if ( l == r )return tree[num].Max;int mid = ( l + r ) >> 1;if ( p <= mid )return query ( p, tree[num].l, l, mid );elsereturn query ( p, tree[num].r, mid + 1, r );
}

区间修改模板(pushup)

首先面对线段树的区间修改,大多数人都会采取lazy,主席树也同样适用
但不同的是各棵树是彼此独立的,如果我们lazy下放,对于i这个状态是对的
但有可能到j状态时的树是不能加上这个lazy的,这个lazy并不是j打上去的
所以不下放,我们就回溯上放回来即可
我们之前说了每个人都小贱小贱的,只会改自己的答案,但是我们有些题贴的是别人的链接,无法帮助别人改答案,就只能等着别人改了答案更新了链接,自己这份才能跟着改,这个链接链上了甩都甩不掉

void pushup ( int root,int len ) {tree[root].sum = tree[tree[root].l].sum + tree[tree[root].r].sum + tree[tree[root].l].lazy * ( len - ( len >> 1 ) ) + tree[tree[root].r].lazy * ( len >> 1 );
}int update ( int root, int l, int r, int L, int R, int val ) {int t = ++ cnt;tree[t].lazy = tree[root].lazy;tree[t].l = tree[root].l;tree[t].r = tree[root].r;tree[t].sum = tree[root].sum;if ( L <= l && r <= R ) {tree[t].lazy = tree[root].lazy + val;return t;}int mid = ( l + r ) >> 1;if ( L <= mid )tree[t].l = update ( tree[root].l, l, mid, L, R, val );if ( mid < R )tree[t].r = update ( tree[root].r, mid + 1, r, L, R, val );pushup ( t, r - l + 1 );return t;
}

区间修改模板(比较特别)

就用求区间和为例吧!!
对于有些奇怪的题,区间是不能pushup和pushdown的,那么就要另辟蹊径代替lazy上下放

我们采取另一种手段来实现修改查询操作,就是每次在区间直接对区间答案进行修改,
然后如果被划分到刚刚好的区间的时候打上lazy即可。
保证了如果区间有lazy那么这整个区间一定是被完全覆盖的

对于一次的修改L,R,lazy的标记只存在与L,R之间划分到的整个区间,
并且所有不是整区间的区间答案在找区间经过的途中都整体增加了该有的值,包括整区间,
然后每次查询答案其答案的初始化都是当前区间的lazy值乘上其区间大小,
因为如果当前区间有lazy那么一定是整体覆盖的,并且查询和修改
要把目标区间进行划分,这样我每次查询和修改都能维护出合理的答案

int update( int & suf, int pre, int l, int r, int L, int R, int val ){suf = ++ cnt;tree[suf].sum = tree[pre].sum;tree[suf].l = tree[pre].l;tree[suf].r = tree[pre].r;tree[suf].lazy = tree[pre].lazy;tree[suf].sum += 1ll * ( R - L + 1 ) * val;if( L <= l && r <= R ) {tree[suf].lazy += val;return suf;}int mid = ( l + r ) >> 1;if( R <= mid ) //全部都在左儿子区间 tree[suf].l = update ( tree[suf].l, tree[pre].l, l, mid, L, R, val );else if( mid < L ) //全部都在右儿子区间 tree[suf].r = update ( tree[suf].r, tree[pre].r, mid + 1, r, L, R, val );else { //左右皆有 tree[suf].l = update ( tree[suf].l, tree[pre].l, l, mid, L, R, val );tree[suf].r = update ( tree[suf].r, tree[pre].r, mid + 1, r, L, R, val );}return suf;
}

区间查询模板

其实亲故们都知道区间查询也可以完成单点的,无非就是多传一个不用的变量罢了
这里假设的是找[L,R][L,R][L,R]之间的最大值

int query ( int L, int R, int num, int l, int r ) {if ( L <= l && r <= R )return tree[num].Max;int mid = ( l + r ) >> 1;int ans1 = - INF, ans2 = - INF;if ( L <= mid )ans1 = query ( L, R, tree[num].l, l, mid );if ( mid < R )ans2 = query ( L, R, tree[num].r, mid + 1, r );return max ( ans1, ans2 );
}

在这里插入图片描述
说了这么多,也该是期末学习成果的展示了!!!
只有做题才能更好的掌握运用主席树模板!在这里插入图片描述

入门题:可持久化线段树

题目

题目描述
为什么说本题是福利呢?因为这是一道非常直白的可持久化线段树的练习题,目的并不是虐人,而是指导你入门可持久化数据结构。

线段树有个非常经典的应用是处理RMQ问题,即区间最大/最小值询问问题。现在我们把这个问题可持久化一下:

Q k l r 查询数列在第k个版本时,区间[l, r]上的最大值
M k p v 把数列在第k个版本时的第p个数修改为v,并产生一个新的数列版本

最开始会给你一个数列,作为第1个版本。每次M操作会导致产生一个新的版本。
修改操作可能会很多呢,如果每次都记录一个新的数列,空间和时间上都是令人无法承受的。
所以我们需要可持久化数据结构
对于最开始的版本1,我们直接建立一颗线段树,维护区间最大值。

修改操作呢?我们发现,修改只会涉及从线段树树根到目标点上一条树链上logn个节点而已,其余的节点并不会受到影响。所以对于每次修改操作,我们可以只重建修改涉及的节点即可。
需要查询第k个版本的最大值,那就从第k个版本的树根开始,像查询普通的线段树一样查询即可。
要计算好所需空间哦

输入格式
第一行两个整数N, Q。N是数列的长度,Q表示询问数
第二行N个整数,是这个数列
之后Q行,每行以0或者1开头,0表示查询操作Q,1表示修改操作M,
格式为
0 k l r 查询数列在第k个版本时,区间[l, r]上的最大值 或者
1 k p v 把数列在第k个版本时的第p个数修改为v,并产生一个新的数列版本

输出格式
对于每个M询问,输出正确答案

样例
input
4 5
1 2 3 4
0 1 1 4
1 1 3 5
0 2 1 3
0 2 4 4
0 1 2 4
output
4
5
4
4
解释
序列版本1: 1 2 3 4
查询版本1的[1, 4]最大值为4
修改产生版本2: 1 2 5 4
查询版本2的[1, 3]最大值为5
查询版本1的[4, 4]最大值为4
查询版本1的[2, 4]最大值为4

数据范围与提示
N <= 10000
Q <= 100000
对于每次询问操作的版本号k保证合法,区间[l, r]一定满足1 <= l <= r <= N

简单题解

说过了这道题是一个入门版题,照着模板打就可以了
对于每一个更改线段树的操作,我们找到操作途中所经过的点,重新建点,
不覆盖原来的线段树模样,对于不变的我们就直接把原来树上的地址直接甩过去就可以了
记录下第i个操作的根节点,每一个线段树都是不相互影响冲突的,只是有可能会共用一些点罢了。

代码实现

#include <cstdio>
#include <iostream>
using namespace std;
#define MAXN 1000005
#define INF 0x7f7f7f7f
struct node {int l, r, Max;
}tree[MAXN << 2];
int cnt, tot, n, q;
int a[MAXN / 10], root[MAXN << 2];
int build ( int l, int r ) {int t = ++ cnt;if ( l == r ) {tree[t].l = tree[t].r = 0;tree[t].Max = a[l];return t;}int mid = ( l + r ) >> 1;tree[t].l = build ( l, mid );tree[t].r = build ( mid + 1, r );tree[t].Max = max ( tree[tree[t].l].Max, tree[tree[t].r].Max );return t;
}
int update ( int num, int p, int v, int l, int r ) {int t = ++ cnt;if ( l == r ) {tree[t].l = tree[t].r = 0;tree[t].Max = v;return t;}int mid = ( l + r ) >> 1;if ( p <= mid ) {tree[t].l = update ( tree[num].l, p, v, l ,mid );tree[t].r = tree[num].r;}else {tree[t].r = update ( tree[num].r, p, v, mid + 1, r );tree[t].l = tree[num].l;}tree[t].Max = max ( tree[tree[t].l].Max, tree[tree[t].r].Max );return t;
}
int query ( int L, int R, int num, int l, int r ) {if ( L <= l && r <= R )return tree[num].Max;int mid = ( l + r ) >> 1;int ans1 = - INF, ans2 = - INF;if ( L <= mid )ans1 = query ( L, R, tree[num].l, l, mid );if ( mid < R )ans2 = query ( L, R, tree[num].r, mid + 1, r );return max ( ans1, ans2 );
}
int main() {scanf ( "%d %d", &n, &q );for ( int i = 1;i <= n;i ++ )scanf ( "%d", &a[i] );root[++ tot] = build ( 1, n );for ( int i = 1;i <= q;i ++ ) {int opt, k, p, v;scanf ( "%d %d %d %d", &opt, &k, &p, &v );if ( opt == 0 )printf ( "%d\n", query ( p, v, root[k], 1, n ) );elseroot[++ tot] = update ( root[k], p, v, 1, n );}return 0;
}

相信这篇博客会帮助大家更好地理解可持久化线段树,如果有错误的地方欢迎大家指出改正,谢谢,联系方式:139红酒白酒葡萄酒+评论
ヾ(ToT)ByeBye
讲得好的话点个赞,亲(づ ̄3 ̄)づ╭❤~
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/318213.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

P3258 [JLOI2014]松鼠的新家

文章目录题意&#xff1a;题解&#xff1a;树上差分代码&#xff1a;树链剖分代码&#xff1a;P3258 [JLOI2014]松鼠的新家题意&#xff1a; n个点&#xff0c;n-1条边&#xff0c;给出每个点的拜访顺序&#xff0c;问每个点经过几次&#xff08;最后一次移动不算拜访&#xf…

[SOCI2005]最大子矩阵(DP) + [JXOI2018]守卫(DP) + [CQOI2016]手机号码(数位DP)[各种DP专练]

DP专练博客 DP专练T1&#xff1a;最大子矩阵题目题解代码实现T2&#xff1a;守卫题目题解代码实现T3&#xff1a;手机号码题目题解代码实现T1&#xff1a;最大子矩阵 题目 这里有一个n*m的矩阵&#xff0c;请你选出其中k个子矩阵&#xff0c;使得这个k个子矩阵分值之和最大。…

IdentityServer4-EF动态配置Client和对Claims授权(二)

本节介绍Client的ClientCredentials客户端模式&#xff0c;先看下画的草图&#xff1a;一、在Server上添加动态新增Client的API 接口。为了方便测试&#xff0c;在Server服务端中先添加swagger&#xff0c;添加流程可参考&#xff1a;https://www.cnblogs.com/suxinlcq/p/67575…

P3178 [HAOI2015]树上操作

P3178 [HAOI2015]树上操作 题意&#xff1a; 题解&#xff1a; 这已经是很裸的树链剖分了。。。 直接套模板 代码&#xff1a; #include<cmath> #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> using namespac…

dotnet core开源博客系统XBlog介绍

XBlog是dotnet core平台下的个人博客开源系统&#xff0c;它只需要通过Copy的方式即可以部署到Linux和windows系统中&#xff1b;如果你有安全证书那只需要简单配置一下即可提供安全的Https服务。接下来主要介绍XBlog功能、部署和基础设置。技术要点基于dotnet core平台&#x…

【莫队/树上莫队/回滚莫队】原理详解及例题:小B的询问(普通莫队),Count on a tree II(树上莫队),kangaroos(回滚莫队)

文章目录问题引入介绍莫队算法及其实现过程时间复杂度莫队算法适用范围莫队奇偶优化普通莫队&#xff1a;小B的询问树上莫队&#xff1a;SP10707 COT2 - Count on a tree II回滚莫队&#xff1a;[PA2011]Kangaroosupd&#xff1a;2021-08-11&#xff1a;重新对博客进行了外观美…

微软 2018 开源大事记

从微软公开宣布 "Microsoft love linux" 那一刻起&#xff0c;过去的几年里&#xff0c;微软积极拥抱开源的举动我们有目共睹&#xff0c;即便有过"Linux is a cancer"这种真香警告的 flag&#xff0c;但不得不承认的是&#xff0c;微软一系列“拥抱开源”…

模板:二叉搜索树平衡树

文章目录前言二叉搜索树代码treap代码splay开点旋转splay插入查找第k大元素查找给定元素的排名前驱&后继删除完整代码练习总结前言 终于开始学这个东西了 看了好几篇博客才找到一篇可读的qwq 我曾经还以为线段树码量大…我真傻&#xff0c;真的 所谓平衡树&#xff0c;就是…

P2486 [SDOI2011]染色

P2486 [SDOI2011]染色 题意&#xff1a; 题解&#xff1a; 与一般的树链剖分相比&#xff0c;不同点在于查询的不是路径上颜色的数量而是颜色段的数量 对于两个颜色段&#xff0c;112和221&#xff0c;两个颜色段数量都是2 如果合在一起颜色段的数量就是3&#xff0c;因为左边…

牛客网CSP-S提高组赛前集训营1题解(仓鼠的石子游戏 [博弈论] + 乃爱与城市的拥挤程度 [树上DP] + 小w的魔术扑克[dfs + 离线])

文章目录T1&#xff1a;仓鼠的石子游戏题目题解代码实现T2&#xff1a;乃爱与城市拥挤程度题目题解代码实现T3&#xff1a;小w的魔术扑克题目题解代码实现T1&#xff1a;仓鼠的石子游戏 题目 仓鼠和兔子被禁止玩电脑&#xff0c;无聊的他们跑到一块空地上&#xff0c;空地上有…

使用PerfView监测.NET程序性能(二):Perfview的使用

在上一篇博客使用PerfView监测.NET程序性能&#xff08;一&#xff09;&#xff1a;Event Trace for Windows 中&#xff0c;我们了解了对Windows及应用程序进行性能分析的基础&#xff1a;Event Trace for Windows (ETW)。现在来看看基于ETW的性能分析工具——Perfview.exePer…

学习有向图和无向图的强连通分量(基本概念+割点+点双联通分量+桥+边双连通分量+全套模板【Tarjan】)

最近总是考到Tarjan&#xff0c;让我措手不及基本概念割点以及点双连通分量Tarjan法求割点推导过程代码实现Tarjan法求点双连通分量推导过程代码实现有向图的Tarjan缩点桥与边双连通分量Tarjan法求桥理论推导代码实现Tarjan法求边双连通分量理论推导代码实现前言&#xff1a;有…

.NET Core下的Spring Cloud——前言和概述

前言前几年一直在写类似dubbo&#xff0c;Spring Cloud的微服务框架辗辗转转重复了多次&#xff0c;也重构推翻了很多次&#xff0c;其中诞生了“Rabbit.Rpc”,”Go”,”RabbitCloud”等开源项目。其中不乏他人对这些项目的完善。很高兴自己的开源项目能够给他人提供思路和复用…

CF785E Anton and Permutation

CF785E Anton and Permutation 题意&#xff1a; 对于一个长度为 n 的序列进行 k 次操作&#xff0c;每次操作都是交换序列中的某两个数。对于每一个操作&#xff0c;回答当前序列中有多少个逆序对。 1<n<200000 1<q<50000 题解&#xff1a; 动态逆序对&#x…

[ NOIP提高组 2016]愤怒的小鸟(暴搜 + 状压DP)// [SNOI2017]一个简单的询问(莫队)

一次性写两道题T1&#xff1a;一个简单的询问题目题解代码实现T2&#xff1a;愤怒的小鸟题目暴搜题解暴搜代码实现状压DP题解状压DP代码实现T1&#xff1a;一个简单的询问 题目 给你一个长度为 N 的序列 ai ,1≤i≤N&#xff0c;和 q 组询问&#xff0c;每组询问读入 l1,r1,l…

微软发布新的 Azure Pipelines 功能和集成

在最近举行的Connect()大会上&#xff0c;微软发布了几项新功能以及与 Azure Pipelines 的集成&#xff0c;包括 Visual Studio Code 的 Azure Pipelines 扩展、GitHub 版本管理、对 IoT 项目的支持以及 ServiceNow 集成。自从 9 月份推出 Azure Pipelines 以来&#xff0c;这种…

年末展望:Oracle 对 JDK收费和.NET Core 给我们的机遇

2018年就结束了&#xff0c;马上就要迎来2019年&#xff0c;这一年很不平凡&#xff0c;中美贸易战还在继续&#xff0c;IT互联网发生急剧变化&#xff0c;大量互联网公司开始裁员&#xff0c;微软的市值在不断上升 &#xff0c;在互联网公司的市值下跌过程中爬到了第一的位置&…

等比数列三角形 (数论 + 黄金分割点)+ JOISC 2016 Day3 T3 「电报」(基环树 + 拓扑排序)

文章目录T1&#xff1a;等比数列三角形题目题解代码实现T2&#xff1a;电报题目题解代码实现T1&#xff1a;等比数列三角形 题目 求三边都是 ≤n 的整数&#xff0c;且成等比数列的三角形个数 注意三角形面积不能为 0 注意 oeis 中未收录此数列&#xff0c;所以并不需要去搜了…

使用PerfView监测.NET程序性能(三):分组

在上一篇博客使用PerfView监测.NET程序性能&#xff08;二&#xff09;&#xff1a;Perfview的使用中&#xff0c;我们通过Perfview帮助文件中自带的代码来简单使用了Perfview&#xff0c;了解了基本操作。现在来看看Perfview中的分组操作&#xff08;Grouping&#xff09;。分…

【做题记录】构造题

CF468C Hack it! 题意&#xff1a; 令 \(F(x)\) 表示 \(x\) 的各个位上的数字之和&#xff0c;如 \(F(1234)123410\) 。 给定 \(a(a\le 10^{18})\) &#xff0c;请求出任意一组 \(l,r(l,r\le 10^{200})\) &#xff0c;要求满足&#xff1a; \[\sum_{il}^{r}F(i)\pmod{a}0 \]输出…