主席树学习笔记

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

概念:

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

用途:

主席树的最基本用途是查询 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(x ->lch, y ->lch, val, l, m);
        y ->rch = x ->rch;
        y ->update();
    }
    else {
        build(x ->rch, y ->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);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值