题解 P3919 【【模板】可持久化数组(可持久化线段树/平衡树)】
luogu3919 【模板】可持久化数组
ps.
鉴于要加深一下可持久化的理解(以及帮助他人更好的理解
再来写一篇题解吧QAQ.
题意:
标题即题意(雾
好了简单说一下吧。
你需要维护这样的一个长度为
-
在某个历史版本上修改某一个位置上的值
-
访问某个历史版本上的某一位置的值
做法:
首先我们撇开区间第k小,再来理解一下可持久化线段树的意思。
通俗理解:很多个线段树按照一定的顺序建立,每个线段树i都和之前的某个有一定联系,需要拷贝一些之前的信息。
比如一个简单的例子:
维护一个数据结构,支持:
- 单点加
- 询问区间和
- 回到之前某个历史状态
其中第三点就是经典的可持久化应用。
解决这一类问题,简单的想法就是每个状态建立一棵线段树,拷贝之前一棵树的信息,并修改相关信息。
但是发现这样空间不够,于是我们考虑舍弃冗余状态,每次只拷贝需要修改的点,这样就能保证每次只新建log级别的点。
然后考虑这个题,发现是上面这个例子的弱化版(大雾
只要把线段树的操作改成单点修改+单点查询即可。
代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<cctype>
#include<cstdlib>
#include<cmath>
#include<queue>
using namespace std;
typedef long long ll;
inline ll read() {
char ch = getchar(); ll x = 0; int op = 1;
for(; !isdigit(ch); ch = getchar()) if(ch == '-') op = -1;
for(; isdigit(ch); ch = getchar()) x = x*10+ch-'0';
return x*op;
}
inline void write(ll a) {
if(a < 0) putchar('-'), a = -a;
if(a >= 10) write(a/10); putchar('0'+a%10);
}
const int N = 1000010, M = 20000010;
int n, m, tot;
int a[N], rt[N], ls[M], rs[M], v[M];
inline void build(int &nk, int l, int r) {
nk = ++ tot;
if(l == r) { v[nk] = a[l]; return; }
int mid = l+r>>1;
build(ls[nk], l, mid); build(rs[nk], mid+1, r);
}
inline void update(int k, int &nk, int l, int r, int x, int y) {
nk = ++ tot; ls[nk] = ls[k]; rs[nk] = rs[k]; v[nk] = v[k];
if(l == r) { v[nk] = y; return; }
int mid = l+r>>1;
if(x <= mid) update(ls[k], ls[nk], l, mid, x, y);
else update(rs[k], rs[nk], mid+1, r, x, y);
}
inline int query(int nk, int l, int r, int x) {
if(l == r) return v[nk];
int mid = l+r>>1;
if(x <= mid) return query(ls[nk], l, mid, x);
else return query(rs[nk], mid+1, r, x);
}
int main() {
n = read(), m = read();
for(int i = 1; i <= n; i ++) a[i] = read();
build(rt[0], 1, n);
for(int i = 1; i <= m; i ++) {
int t = read(), opt = read(), x = read(), y;
rt[i] = rt[t];
if(opt == 1) {
y = read(); update(rt[i], rt[i], 1, n, x, y);
} else write(query(rt[t], 1, n, x)), puts("");
} return 0;
}
拓展理解
别急,还没结束。。。
我们再来理解一下区间第k小的主席树操作吧。
仍然是可持久化线段树,但是和上面两种例子都是不太相似的。
主席树求区间第k小,我们需要建立的是权值线段树,于是需要先离散化。
在原序列上建立主席树,每一棵权值线段树i,拷贝第i-1棵线段树的信息,并在上面修改(
这样我们得到的是一个类似前缀和的主席树。
于是我们可以通过
所以,虽说都是可持久化线段树,还是有比较大的区别的。
特别注意不要把可持久化线段树理解为就等于权值线段树~~~
_the end_
by bestFy 2018.1