浙江集训Day4,从早8:00懵B到晚21:00,只搞懂了可持久化线段树以及主席树的板子。今天只能记个大概,以后详细完善讲解。
可持久化线段树指的是一种基于线段树的可回溯历史状态的数据结构。我们想要保存某一段序列的历史信息,可以朴素地开m倍的空间存储;而线段树是一种优秀的数据结构,它每次修改操作只把原先的数据修改log个节点,这就意味着我们只增加这些被修改的节点存储新信息就好了。传统的线段树用二叉树存储,但是因为涉及加点,可持久化线段树必须维护一个自增的tot动态成为新点的编号。对于每个节点,我们要维护两个指针指向它的左右子节点。对于每次修改,我们把新节点的不被修改的一半区间指向上个状态该区间的节点编号,等同于利用了上次的区间信息;另一半新开一个点继续递归下去即可。
可持久化线段树一般不能维护区间修改,因为多个根共享子节点,我们没有办法对每个状态的树独立维护它的lazy_tag。
代码(动态开点&&区间最大值):
- #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;
- }
听说主席树是由fotile96主席发明的(总之是位神犇就对了)。目前除了静态区间第K小值问题我还没听懂别的应用。
给定一段区间,每次询问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数问题中,我们是用类似前缀和的思想用主席树维护了虚拟的权值树来查询对应区间信息的。
代码:
- #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;
- }