可持久化线段树(主席树)

主席树(可持久化线段树)

->模板地址

->浅谈可持久化

(为便于理解,以下演示代码均使用数组版,不过我推荐还是用指针)

(由于习惯问题,本文的代码可能和别人写的有出入,看懂就行)

概述

可持久化线段树,也叫主席树、函数式线段树,是重要的可持久化数据结构,可持久化线段树主要支持区间查询单点修改(区间修改主要使用标记永久化实现,不然会浪费大量空间,本文不讨论)

可持久化线段树的主要难点不在于对这个数据结构的理解,而是应用,所以本文不在代码上花太多篇幅

线段树合并等也要用到函数式线段树

主要思想

和其它可持久化数据结构一样,可持久化线段树对于每次因单点修改发生变化的节点新建一个节点而不是直接在节点上修改,这样就可以查询每个历史版本的信息

存储结构

(为使结构更加清晰,本文使用结构体)

因为可持久化线段树不在是一棵完全二叉树,我们需要记录每个节点的左右儿子,同时因为节点个数较多,为了省空间,我们不在记录节点所代表的区间[l,r],而是作为递归参数传递

可持久化线段树需要动态开点

struct node {
    int son[2];
    int v;
    node() {
        son[0] = son[1] = 0;
    }
}t[1000001];
void push_up(int p, int l, int r) {
    /*do something*/
}
int tot = 0;
int new_node() {
    return ++ tot;
}

基本操作

建树

这个和普通的线段树几乎一模一样,只不过有时需要建立一棵具有所有节点的线段树(这个代码就不放了),有时只需要有一条链上的节点

//建立一棵x为c的线段树
void build(int &p, int l, int r, int x, int c) {
    if(! p)p = new_node();//如果节点为空,则新建节点
    if(l == r)return void(t[p].v = c);
    int mid = (l + r) >> 1;
    if(x <= mid)build(t[p].son[0], l, mid, x, c);
    else build(t[p].son[1], mid + 1, r, x, c);
    push_up(p, l, r);
}

还有一种方法是直接建树使返回新建的节点(这里不贴代码)

区间查询

区间查询也和不同的线段树差不多,只不过查询的入口要为需要查询的历史版本的根(这个也不放代码了,直接复制普通线段树的代码,只不过注意点不开满的线段树访问空节点时要直接返回)

单点修改

这个就是体现主席树和普通线段树不同的地方了,主席树需要对于每个访问到的节点新建节点,并且还要多一个参数表示被修改的版本

void update(int &p, int rt, int l, int r, int x, int c) {
    if(! rt)return void(build(p, l, r, x, c));//被修改的版本不存在该节点,直接建树(不要也没有什么问题,但不加这个指针可能会RE)
    if(! p)p = new_node();
    if(l == r)return void(t[p].v += c);
    int mid = (l + r) >> 1;
    if(x <= mid)update(t[p].son[0], t[rt].son[0], l, mid, x, c);
    else update(t[p].son[1], t[rt].son[1], mid + 1, r, x, c);
    push_up(p, l, r);
}

应用

一般有几种题型要想到主席树:

  • 满足区间推导但不满足区间可加性的部分题目(静态区间第k小)
  • 可以用线段树合并的题目(树上离线查询)
  • 区间的区间查询(查询区间内大于某个数的数的数量)

转载于:https://www.cnblogs.com/akakw1/p/9896297.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值