【数据结构模版】可持久化线段树 && 主席树
浙江集训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线段树+权值线段树+主席树及一些例题
“队列进出图上的方向 线段树区间修改求出总量 可持久留下的迹象 我们 俯身欣赏” ----<膜你抄> 线段树很早就会写了,但一直没有总结,所以偶尔重写又会懵逼,所以还是要总结一下. ...
随机推荐
- ZooKeeper CentOS7上安装
下载http://www.apache.org/dyn/closer.cgi/zookeeper(我下的是zookeeper-3.4.14) 1.创建 /usr/local/services/zook ...
- JWT安装配置
1.1 安装JWT pip install djangorestframework-jwt==1.11.0 1.2 syl/settings.py 配置jwt载荷中的有效期设置 import date ...
- 【API进阶之路】API带来的微创新,打动投资人鼓励我创业
摘要:怎么帮助创作者提高视频的推荐量呢?我发现了:视频的封面图非常重要. 上回说到,老板一拍脑门,交代了一个新项目:小成本开发一款短视频剪辑工具([<[API进阶之路]人少钱少需求多的新项目该怎 ...
- 矩阵连乘问题的算法复杂度的计算--卡塔兰数(Catalan数)的数学推导和近似公式
author: cust-- ZKe --------------------- 这里以连乘积加括号问题为背景: 由于矩阵的乘积满足结合律,且矩阵乘积必须满足左边矩阵的列数的等于右边矩阵的行数,不同的 ...
- 最简单的基于FFmpeg的直播系统开发移动端例子:IOS 视频解码器
本文记录IOS平台下基于FFmpeg的视频解码器.该示例C语言的源代码来自于<最简单的基于FFMPEG+SDL的视频播放器>.相关的概念就不再重复记录了. 源代码 项目的目录结构如图所示. ...
- Aps.Net Core3.1 WebApi发送阿里云短信验证码
1.前言 转眼又要过了一年了 好久没写博客了,人不学就要落后,今天有时间把以前弄的发送阿里云短信验证码登录记录一下. 2.准备条件 1)去阿里云官网注册一个账号.有账号直接登录就行,以前新人好像有免费 ...
- SQL:获取每个key下最新创建的记录
今天遇到了一个好玩的问题 问题: 有一个含有key和createdTime字段的表,表里存在很多不同的key值,每个key值下有很多记录. 我想要查出每个key下面cratedTime最大的记录,即每 ...
- 【转】Lisp的本质
Lisp的本质: http://www.csdn.net/article/2012-11-22/2812113-The-Nature-Of-Lisp###
- PHP-Parse 简介以及在 Hyperf 中的应用
介绍 PHP-Parse 是分析 PHP 代码生成 AST 的库,分析出可读性很高的对象数据结构,方便后续的更新和遍历. PHP-Parse 的主要作用是修改原有代码(比如插入自定义的代码片段),生成 ...
- tcp 保活定时器分析 & Fin_WAIT_2 定时器
tcp keepalive定时器 http server 和client端需要防止"僵死"链接过多!也就是建立了tcp链接,但是没有报文交互, 或者client 由于主机突然掉电! ...