可持久化线段树(主席树)学习笔记

原创 2018年04月15日 16:10:26
0x00写在前面

在接触可持久化线段树前一直以为这是一个高大上的东西,非常复杂.但是在学习之后才发现它远比想象中容易,甚至比线段树还简单,所以学的时候不要有心理压力.

0x01从一道题开始

洛谷P3834
题意:给你一个序列,多次询问区间第k大的数是多少(序列长度与询问个数为1e5级别)
可以考虑这样一种算法:对于每个前缀[1,i]维护一颗权值线段树,每次询问[l,r]时只需用前缀[1,r]减去前缀[1,l1]的线段树的对应节点就可以得到[l,r]的权值线段树,就可以轻松得到区间第k小.
但是建这么多线段树显然是不可能的,通过观察我们发现相邻两颗线段树都只相差一次修改,即[1,i]是在[1,i1]的基础上插入i处的数得到的.因此我们考虑利用这一性质,这就引出的本文的主题:可持久化线段树.

0x02可持久化线段树简介

一次修改在线段树上的表现则是对从根开始到相应叶子节点的一次修改,那么我们考虑保留原有线段树,每次修改新建一条路径,新线段树的根就是这条路径的起点.
原有的线段树
新建一条路径
新的线段树
如图所示,在对一个维护[1,10]序列的线段树进行修改(被修改的位置为7)时,我们新建了一条从根节点到[7,7]的路径,黄色边代表新建的点利用了原有的线段树的节点,红色的边代表路径上的边,我们可以用一份伪代码来描述这个过程:

void ins(原有线段树的根节点pre,新线段树的根节点rt,被修改的点的编号x,当前区间的左端点l,右端点r)
{
    新建一个节点,rt等于该节点//①
    修改操作
    if (x<=区间中点mid)
    {
        ins(pre的左儿子,rt的左儿子(此时它还是空的,在①处被新建),x,l,mid);
        rt的右儿子=pre的右儿子(利用原树节点)//②
    }
    else
    {
        ins(pre的右儿子,rt的右儿子,x,mid+1,r);
        rt的左儿子=pre的左儿子//②
    }
}

实际实现时可以一开始直接把pre赋值给rt,从而省去②操作

    void ins(int pre,int &rt,int x,int v,int l,int r)//给x处的数加上v
    {
        rt=++cnt;
        tr[rt]=tr[pre];//rt中存放着数的信息
        tr[rt].sum+=v;
        if (l>=r) return;
        if (x<=mid) ins(tr[pre].l,lson,x,v,l,mid);
        else ins(tr[pre].r,rson,x,v,mid+1,r);
    }

询问操作跟跟普通线段树没什么区别

    int query(int rt,int x,int l,int r)
    {
        if (l>x) return 0;
        if (r<=x) return tr[rt].sum;
        return query(lson,x,l,mid)+query(rson,x,mid+1,r);
    }
0x04带修改的可持久化线段树

类似于树状数组,每次询问和修改我们可以额外花费log()的代价,类似下面的操作

    void ins(LL x,LL v,LL key)
    {
        for(;x && x<=n;x+=lb(x))
            xg(bit[x],v,key,1,sz);
    }
    LL ask(LL x,LL k) //[1,x]<=k
    {
        LL res=0;
        for(;x;x-=lb(x))
            res+=query(bit[x],k,1,sz);
        return res;
    }
0x05注意事项

1.你可以把它想象成一个数组,它支持区间询问和单点修改,并支持访问所有历史版本.
因此合理安排修改顺序很关键.
2.注意空间大小,一不小心就会RE或者MLE.

0x06例题

1.
bzoj1901
裸的带修改主席树
代码
2.
bzoj3295
用带修改主席树维护每个前缀的权值线段树,删除时修改即可.
代码
3.
bzoj3932
裸的带修改线段树
代码
4.
bzoj2809
先搞出dfs序,每一个区间就是一个子树,然后枚举领导,主席树上二分能选到的最多人数.
5.
spoj_dquery
每个前缀维护一棵线段树,其中的节点i代表从i到当前点的颜色数目(假如颜色都不一样就是后缀和),每加一个点,last[col]代表它上一次出现的位置,若last[col]>0则在last[col]处减去1.
代码
6.
bzoj4771
先考虑没有深度限制的情况,可以用差分的方法,每个相同颜色的点处加1,在dfs序上相邻两个点lca处减1,那么每个子树的答案就是子树和.然后按深度顺序依次加点,就可以解决有深度限制的问题,对于每个x,d,在deep[x]+d深的子树里找即可.
代码

版权声明: https://blog.csdn.net/qq_16267919/article/details/79950173

主席树(可持久化线段树)入门专题

1.poj 2104 查询区间第k小。 主席树其实相当于建立了n棵线段树,第i棵线段树是根据区间【1,i】按值建立的。对于每一棵线段树我们记录它对应的区间每个数出现的次数,所以首先要对所有的数离散化。...
  • HTT_H
  • HTT_H
  • 2015-08-16 18:34:52
  • 4145

【洛谷3834】 【模板】可持久化线段树 (主席树)

题面具体题目不再叙述,参考洛谷 题目大意,求区间[l,r]中第k大的树题解主席树很经典的运用 首先将值离散化之后,构建一颗值域线段树 储存区间和 0版本的线段树是空树 每次在值域上增加1就重...
  • qq_30974369
  • qq_30974369
  • 2017-08-21 23:40:07
  • 216

主席树(可持久化线段树)学习笔记

首先离散化 然后当前版本的线段树可以有上个版本的线段树修改lgn个节点得到的 还满足区间减法 好神 #include #include #include #include #includ...
  • liutian429073576
  • liutian429073576
  • 2015-12-20 09:45:10
  • 719

可持久化线段树附图解

可持久化线段树
  • chen1352
  • chen1352
  • 2016-08-13 20:25:28
  • 345

【可持久化线段树】【主席树】[HDU4417]Super Mario

Mario is world-famous plumber. His “burly” figure and amazing jumping ability reminded in our memory...
  • JeremyGJY
  • JeremyGJY
  • 2016-01-24 15:23:49
  • 508

POJ 2104 kth number 主席树(可持久化线段树)[指针实现]

大家都很强,可与之共勉。我不会告诉你们我的输出优化错了,然后调了半天,Woc! 网上几乎都是数组实现的线段树与主席树,我就做一股清(zhuo)流好了。 题目是不带修改的查询区间第k大,注意主席树维...
  • simpsonk
  • simpsonk
  • 2017-03-20 11:53:43
  • 351

【HDU4348】To The Moon-主席树(可持久化线段树)区间修改+区间询问

【HDU4348】To The Moon-主席树(可持久化线段树)区间修改+区间询问
  • Maxwei_wzj
  • Maxwei_wzj
  • 2017-03-15 11:28:33
  • 187

Codeforces 650D. Zip-line (动态LIS) (可持久化线段树 或 离线+树状数组)

题意: 给定一个长度为n的数列,和m个询问,每个询问的格式是:将原数组的第a个数改成b之后,数组的最长上升子序列(LIS)的长度。 做法:可持久化线段树 令 h[ ] 为原数组,LIS_L[ i ...
  • u012891242
  • u012891242
  • 2016-03-11 17:38:45
  • 1450

主席树——可持久化权值线段树

前言很久以前就听说过主席树这个名字,当时听到的时候还只能一脸懵逼,不知道是什么意思。而现在学习了“线段树”之后终于可以对这种神奇的“线段树”进行学习。...
  • GGN_2015
  • GGN_2015
  • 2017-04-05 11:18:11
  • 376

可持久化线段树——主席树

支持访问任意历史版本以及在历史版本上修改的数据结构
  • jinmingyi1998
  • jinmingyi1998
  • 2017-08-14 18:32:49
  • 93
收藏助手
不良信息举报
您举报文章:可持久化线段树(主席树)学习笔记
举报原因:
原因补充:

(最多只允许输入30个字)