首页 > 学院 > 开发设计 > 正文

主席树学习笔记

2019-11-08 01:01:31
字体:
来源:转载
供稿:网友

今天学习了一个特殊的线段树结构,主席树。

概念:

主席树是一种可持久化的线段树结构,然后没了= =

用途:

主席树的最基本用途是查询a[l]……a[r]x∈[L,R]x的个数。 引申还可以查询区间中第K小的数是什么。等等……

结构:

对于主席树的结构理解,我自己阅读多篇他人的博客,才终于明白了是怎么一回事。不知是博客写的不好还是我的理解能力太差= =大概是后者吧。 那我就按照自己的理解来讲清楚这究竟是什么样的结构。

初步结构要点:
1、主席树可以理解为线段树的集合。也就是说,很多棵线段树合起来叫主席树(先这么理解好了)2、对于每棵线段树,它保存的是大小属于[L, R]的数的个数(先这么理解好了)3、线段树之间的区别就是线段树保存的数据范围a[i]不同, 记线段树T[i]表示对于原序列a[1]……a[i]前缀中,大小属于[L, R]的数的个数。(不晓得自己有没有讲清楚)
初步结构分析:
1、线段树的范围应该是1……maxN(maxN表示序列中的最大值)2、一棵线段树的理论空间大小是O(2maxN),于是主席树的理论空间大小是O(N*maxN),N是序列长度。(对于每个前缀序列,都有一棵线段树,一共N个前缀)3、对于任意的两棵线段树,树中对应的节点表示的含义相同,即[l, r]的数的个数;只是数据范围不同,它们对应着两个包含与被包含的前缀。所以T[i]的范围小于T[j](i < j)。因为对应的节点含义相同,所以对应的节点可以相加减。T[j] - T[i - 1]应该表示着a[i]……a[j]范围中,属于[L, R]的数的个数(i < j)。
进阶分析I:

鉴于a[i]的大小可能会非常大(eg. a[i] <= 109), 我们可以先将原序列离散化成N个数据点,再将N个数据点做成线段树。这样线段树的空间为O(N), 因此主席树的空间为O(N2)

进阶分析II:

分析T[i]与T[i + 1]。它们实际相差的范围只是后者比前者在数据范围中多了一个a[i + 1]。 分析T[i]到T[i + 1]的动态变化,其实只是从线段树的根节点到某个叶子节点这么一条路径发生了变化(保存的值都加了一)。于是利用指针,T[i + 1]的大部分节点都可以沿用T[i],只是少量节点需要重新开辟(变化的节点)。 可以想象,这样就把一棵棵独立的线段树联系成了一个整体 -> 主席树。至于主席树究竟长成什么样,有点复杂,但是有一点是可以明确的,两棵相邻的线段树关系是非常清楚的。 由于大量地沿用了之前地节点,主席树的空间复杂度大大降低变成O(NlogN),变成了可以承受的范围了。

总结:

至此,一棵主席树就搭建完成了。关于时间复杂度,其实也就是两次线段树的复杂度,因此时间复杂度是O(NlogN)


代码:

const int maxn = 1e5 + 5;int a[maxn], a2[maxn]; //a为原序列,a2为排序离散化序列struct Node { Node *lch, *rch; int sz; Node() {lch = rch = NULL, sz = 0;} Node (Node *l, Node *r, int _sz) : sz(_sz) {lch = l, rch = r;} void update() { if (lch != NULL) sz += lch ->sz; if (rch != NULL) sz += rch ->sz; }};Node *tp = new Node();Node *root[maxn] = {NULL};void build(Node *&x, Node *&y, int val, int l, int r) { if (x == NULL) x = tp; y = new Node(); int m = (l + r) >> 1; if (l == r) { *y = *x; y ->sz ++; return; } if (val <= a2[m]) { build(y ->lch, x ->lch, val, l, m); y ->rch = x ->rch; y ->update(); } else { build(y ->rch, x ->rch, val, m + 1, r); y ->lch = x ->lch; y ->update(); }}int query(Node *&L, Node *&R, int l, int r, int k) { if (L == NULL) L = tp; if (R == NULL) R = tp; if (l == r) return a2[l]; int m = (l + r) >> 1; int ans = 0; if (R ->lch) ans += R ->lch ->sz; if (L ->lch) ans -= R ->lch ->sz; if (ans >= k) return query(L ->lch, R ->lch, l, m, k); else return query(L ->rch, R ->rch, m + 1, r, k - ans);}
上一篇:什么是光栅显示

下一篇:最少步数

发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表