【数据结构模版】可持久化线段树 && 主席树
浙江集训Day4,从早8:00懵B到晚21:00,只搞懂了可持久化线段树以及主席树的板子。今天只能记个大概,以后详细完善讲解。
可持久化线段树指的是一种基于线段树的可回溯历史状态的数据结构。我们想要保存某一段序列的历史信息,可以朴素地开m倍的空间存储;而线段树是一种优秀的数据结构,它每次修改操作只把原先的数据修改log个节点,这就意味着我们只增加这些被修改的节点存储新信息就好了。传统的线段树用二叉树存储,但是因为涉及加点,可持久化线段树必须维护一个自增的tot动态成为新点的编号。对于每个节点,我们要维护两个指针指向它的左右子节点。对于每次修改,我们把新节点的不被修改的一半区间指向上个状态该区间的节点编号,等同于利用了上次的区间信息;另一半新开一个点继续递归下去即可。
可持久化线段树一般不能维护区间修改,因为多个根共享子节点,我们没有办法对每个状态的树独立维护它的lazy_tag。
代码(动态开点&&区间最大值):
- #include <iostream>
- #include <cstdio>
- #include <cstring>
- #include <algorithm>
- #include <climits>
- #define maxn 1000100
- #define maxm 1000100
- #define LG 21
- #define BUG putchar('*')
- using namespace std;
- template <typename T>
- void read(T &x) {
- x = 0;
- int f = 1;
- char ch = getchar();
- while (!isdigit(ch)) {
- if (ch == '-')
- f = -1;
- ch = getchar();
- }
- while (isdigit(ch)) {
- x = x * 10 + (ch ^ 48);
- ch = getchar();
- }
- x *= f;
- return;
- }
- int n, m, a[maxn];
- namespace Segment_tree {
- int root[maxm], tot;
- struct node {
- int dat, lc, rc;
- } seg[maxn * 20];
- inline void update(int nd) {
- seg[nd].dat = max(seg[seg[nd].lc].dat, seg[seg[nd].rc].dat);
- }
- int build(int l, int r) {
- int nd = ++tot;
- if (l == r) {
- seg[nd].dat = a[l];
- return nd;
- }
- int mid = (l + r) >> 1;
- seg[nd].lc = build(l, mid), seg[nd].rc = build(mid + 1, r);
- update(nd);
- return nd;
- }
- int modify(int nd, int l, int r, int x, int val) {
- int now = ++tot;
- seg[now] = seg[nd];
- if (l == r) {
- seg[now].dat = val;
- return now;
- }
- int mid = (l + r) >> 1;
- if (x <= mid)
- seg[now].lc = modify(seg[nd].lc, l, mid, x, val);
- else seg[now].rc = modify(seg[nd].rc, mid + 1, r, x, val);
- update(now);
- return now;
- }
- int query(int nd, int l, int r, int ql, int qr) {
- if (ql <= l && qr >= r)
- return seg[nd].dat;
- if (ql > r || qr < l)
- return INT_MIN;
- int mid = (l + r) >> 1;
- return max(query(seg[nd].lc, l, mid, ql, qr), query(seg[nd].rc, mid + 1, r, ql, qr));
- }
- } using namespace Segment_tree;
- int main() {
- read(n), read(m);
- for (int i = 1; i <= n; ++i)
- read(a[i]);
- root[0] = build(1, n);
- int k, op, val, loc, l, r;
- for (int i = 1; i <= m; ++i) {
- read(k), read(op);
- if (op == 1) {
- read(loc), read(val);
- root[i] = modify(root[k], 1, n, loc, val);
- } else {
- read(l), read(r);
- printf("%d\n", query(root[k], 1, n, l, r));
- root[i] = root[k];
- }
- }
- return 0;
- }
听说主席树是由fotile96主席发明的(总之是位神犇就对了)。主要用于维护数据区间的值域问题。
给定一段区间,每次询问l,r区间内的第k小值。主席树是一个可持久化权值线段树:我们将右端点r抽象成时刻,建立(数据长度)个根,以root[t]表示区间[1, t]所维护的权值线段树的根。(模板题的权值需要离散化)如果查询[l, r],我们找出第l-1和第r棵线段树(将root[0]建成一棵空树),这两棵树对应节点信息做差,实质上就得到了一棵仅维护区间[l, r]的虚拟线段树的信息。我们动态跑出这棵树的同时二分它的左右子树:如果这棵虚拟树的左端点权值和sum大于等于k,说明第k大数在[l, mid]中,我们转而查询这段的第k大:如果sum小于k,说明所求数是区间[mid + 1, r]的第k-sum大数。实际上,在区间Kth数问题中,主席树以类似前缀和的思想维护了虚拟的维护[l, r]信息的权值线段树来查询对应区间信息的。
代码:
- #include <iostream>
- #include <cstdio>
- #include <cstring>
- #include <algorithm>
- #include <climits>
- #define maxn 200100
- #define maxm 200100
- #define LG 21
- #define BUG putchar('*')
- using namespace std;
- int n, m;
- template <typename T>
- void read(T &x) {
- x = 0;
- int f = 1;
- char ch = getchar();
- while (!isdigit(ch)) {
- if (ch == '-')
- f = -1;
- ch = getchar();
- }
- while (isdigit(ch)) {
- x = x * 10 + (ch ^ 48);
- ch = getchar();
- }
- x *= f;
- return;
- }
- namespace Contra {
- int st[maxn];
- int work(int *org, int *st) {
- for (int i = 1; i <= n; ++i)
- st[i] = org[i];
- sort(st + 1, st + 1 + n);
- int len = unique(st + 1, st + n + 1) - (st + 1);
- for (int i = 1; i <= n; ++i)
- org[i] = lower_bound(st + 1, st + len + 1, org[i]) - st;
- return len;
- }
- }
- int a[maxn], N;
- namespace President_tree {
- #define lc(i) seg[(i)].lc
- #define rc(i) seg[(i)].rc
- using namespace Contra;
- int tot, root[maxm];
- struct node {
- int cnt, lc, rc;
- } seg[maxm * 20];
- void update(int nd) {
- seg[nd].cnt = seg[lc(nd)].cnt + seg[rc(nd)].cnt;
- }
- int build(int l, int r) {//root[0]:empty_tree
- int nd = ++tot;
- if (l == r) return nd;
- int mid = (l + r) >> 1;
- seg[nd].lc = build(l, mid);
- seg[nd].rc = build(mid + 1, r);
- return nd;
- }
- int modify(int pre, int l, int r, int x) {
- int nd = ++tot;
- seg[nd] = seg[pre];
- if (l == r) {
- ++seg[nd].cnt;
- return nd;
- }
- int mid = (l + r) >> 1;
- if (x <= mid)
- lc(nd) = modify(lc(pre), l, mid, x);
- else
- rc(nd) = modify(rc(pre), mid + 1, r, x);
- update(nd);
- return nd;
- }
- int query(int ql, int qr, int l, int r, int k) {
- if (l == r) {
- return st[l];
- }
- int mid = (l + r) >> 1, sum = seg[lc(qr)].cnt - seg[lc(ql)].cnt;
- if (k <= sum)
- return query(lc(ql), lc(qr), l, mid, k);
- return query(rc(ql), rc(qr), mid + 1, r, k - sum);
- }
- void init() {
- N = work(a, st);
- root[0] = build(1, N);
- for (int i = 1; i <= n; ++i) {
- root[i] = modify(root[i - 1], 1, N, rk[i]);
- }
- }
- } using namespace President_tree;
- int main() {
- read(n), read(m);
- for (int i = 1; i <= n; ++i)
- read(a[i]);
- init();
- int l, r, k;
- for (int i = 1; i <= m; ++i) {
- read(l), read(r), read(k);
- printf("%d\n", query(root[l - 1], root[r], 1, N, k));
- }
- return 0;
- }
【数据结构模版】可持久化线段树 && 主席树的更多相关文章
- 线段树简单入门 (含普通线段树, zkw线段树, 主席树)
线段树简单入门 递归版线段树 线段树的定义 线段树, 顾名思义, 就是每个节点表示一个区间. 线段树通常维护一些区间的值, 例如区间和. 比如, 上图 \([2, 5]\) 区间的和, 为以下区间的和 ...
- [学习笔记] 可持久化线段树&主席树
众所周知,线段树是一个非常好用也好写的数据结构, 因此,我们今天的前置技能:线段树. 然而,可持久化到底是什么东西? 别急,我们一步一步来... step 1 首先,一道简化的模型: 给定一个长度为\ ...
- 权值线段树&&可持久化线段树&&主席树
权值线段树 顾名思义,就是以权值为下标建立的线段树. 现在让我们来考虑考虑上面那句话的产生的三个小问题: 1. 如果说权值作为下标了,那这颗线段树里存什么呢? ----- 这颗线段树中, 记录每个值出 ...
- 洛谷P3834 可持久化线段树(主席树)模板
题目:https://www.luogu.org/problemnew/show/P3834 无法忍受了,我要写主席树! 解决区间第 k 大查询问题,可以用主席树,像前缀和一样建立 n 棵前缀区间的权 ...
- bzoj 4408: [Fjoi 2016]神秘数 数学 可持久化线段树 主席树
https://www.lydsy.com/JudgeOnline/problem.php?id=4299 一个可重复数字集合S的神秘数定义为最小的不能被S的子集的和表示的正整数.例如S={1,1,1 ...
- 牛客网 暑期ACM多校训练营(第一场)J.Different Integers-区间两侧不同数字的个数-离线树状数组 or 可持久化线段树(主席树)
J.Different Integers 题意就是给你l,r,问你在区间两侧的[1,l]和[r,n]中,不同数的个数. 两种思路: 1.将数组长度扩大两倍,for(int i=n+1;i<=2* ...
- [POJ2104] K – th Number (可持久化线段树 主席树)
题目背景 这是个非常经典的主席树入门题--静态区间第K小 数据已经过加强,请使用主席树.同时请注意常数优化 题目描述 如题,给定N个正整数构成的序列,将对于指定的闭区间查询其区间内的第K小值. 输入输 ...
- 学习笔记--函数式线段树(主席树)(动态维护第K极值(树状数组套主席树))
函数式线段树..资瓷 区间第K极值查询 似乎不过似乎划分树的效率更优于它,但是如果主席树套树状数组后,可以处理动态的第K极值.即资瓷插入删除,划分树则不同- 那么原理也比较易懂: 建造一棵线段树(权值 ...
- 线段树(单标记+离散化+扫描线+双标记)+zkw线段树+权值线段树+主席树及一些例题
“队列进出图上的方向 线段树区间修改求出总量 可持久留下的迹象 我们 俯身欣赏” ----<膜你抄> 线段树很早就会写了,但一直没有总结,所以偶尔重写又会懵逼,所以还是要总结一下. ...
随机推荐
- npm的使用说明
博主是刚开始写项目的前端小白菜,边学边整理,以供后面的小猿参考,共同进步. 首先: npm的官网地址:https://www.npmjs.com Windows 安装包(.msi) 32 位安装包下载 ...
- day1-linux基础命令
1.创建文件 ①touch 1.txt ②echo > 2.txt ③vim 3.txt 以上方式都能直接创建文件 批量创建文件 2.创建目录 ①mkdir /software ②创建连续目录 ...
- Java Arrays 和 List的相互转化
最近在 leetcode 刷题的时候遇到过好几次这样的情况:需要返回的数据类型是数组(Arrays),但是求解的时候并不知道数组的长度,这时候就需要先用 List 进行临时存储,最后再转化为 Arra ...
- Hadoop基础------>MR框架-->WordCount
认识Mapreduce Mapreduce编程思想 Mapreduce执行流程 java版本WordCount实例 1. 简介: Mapreduce源于Google一遍论文,是谷歌Mapreduce的 ...
- Java学习的第四十三天
1.例5.1数组元素的引用 public class cjava { public static void main(String[] args) { int i; int []a=new int[1 ...
- Go语言中的互斥锁和读写锁(Mutex和RWMutex)
目录 一.Mutex(互斥锁) 不加锁示例 加锁示例 二.RWMutex(读写锁) 并发读示例 并发读写示例 三.死锁场景 1.Lock/Unlock不是成对出现 2.锁被拷贝使用 3.循环等待 虽然 ...
- Java安全框架(一)Spring Security
Java安全框架(一)Spring Security 文章主要分三部分 1.Spring Security的架构及核心组件:(1)认证:(2)权限拦截:(3)数据库管理:(4)权限缓存:(5)自定 ...
- Java导出Pdf格式表单
前言 作为开发人员,工作中难免会遇到复杂表单的导出,接下来介绍一种通过Java利用模板便捷导出Pdf表单的方式 模拟需求 需求:按照下面格式导出pdf格式的学生成绩单 准备工作 Excel软件 ...
- SpringBoot第四集:整合JdbcTemplate和JPA(2020最新最易懂)
SpringBoot第四集:整合JdbcTemplate和JPA(2020最新最易懂) 当前环境说明: Windows10_64 Maven3.x JDK1.8 MySQL5.6 SpringTool ...
- 典型分布式系统分析:Dynamo
本文是典型分布式系统分析系列的第四篇,主要介绍 Dynamo,一个在 Amazon 公司内部使用的去中心化的.高可用的分布式 key-value 存储系统. 在典型分布式系统分析系列的第一篇 MapR ...