首页 > 其他分享 >【学习笔记】Segment Tree Beats 学习笔记

【学习笔记】Segment Tree Beats 学习笔记

时间:2023-02-26 16:37:07浏览次数:54  
标签:rt log 标记 int 复杂度 Tree 笔记 Beats 节点

前置知识:线段树常规操作的复杂度证明

单点修改、查询

线段树高为 \(O(\log n)\),因此单点操作复杂度单次 \(O(\log n)\),总复杂度 \(O(q\log n)\)。

区间修改、查询

在懒标记的基础上,区间修改和查询操作本质是相同的,设当前操作的区间是 \([L,R]\),线段树所在节点对应区间 \([l,r]\),则有三种情况:

  • \([l,r]\) 与 \([L,R]\) 无交,退出函数

  • \([l,r]\) 为 \([L,R]\) 子区间,执行操作并退出函数

  • \([l,r]\) 与 \([L,R]\) 有交,向左右儿子节点继续执行操作

容易发现第一种情况和第二种情况都来自第三种情况,其数量不会超过第三种的 \(2\) 倍,因此我们只需要证明第三种情况的复杂度。

在每一深度的线段树中,至多有两个区间会与 \([L,R]\) 有交且不为子区间,因此每个深度之后有两个区间为第三种情况,树高 \(O(\log n)\),因此总复杂度 \(O(q\log n)\)。

线段树合并

设初始线段树的叶子节点数量 \(m\),线段树规模 \(n\)。

按照上面的思考方式,合并时的重复节点会产生对复杂度的影响。

考虑合并时重复节点个数是不超过较小一棵线段树的,因此每个节点的合并次数不超过 \(\log n\),因此时间复杂度 \(O(m\log n)\)。

同时“可持久化”的线段树合并中,每次新建节点也是在出现重复节点时,因此空间复杂度 \(O(m\log n)\)。

区间最值操作

不含区间加减的情况

HDU-5306 Gorgeous Sequence

含有区间取 \(\min\),区间求和和区间求 \(\max\) 三个操作。

实现方法

对线段树每个节点,维护和 \(sum\),最大值 \(mx\),最大值个数 \(cnt\),严格最大值 \(sec\),以及区间取 \(\min\) 的标记 \(tag\)。

区间修改与 \(k\) 取 \(\min\) 时,讨论不同情况:

  • 若 \(mx\le k\),则不会产生影响,直接退出

  • 若 \(sec<k<mx\),则只有 \(mx\) 会受到影响,直接修改并打上标记 \(tag\)

  • 若 \(k\le sec\),则继续向下操作

同时下传标记时,由于打上标记的充要条件时 \(sec<k<mx\),因此当前子区间的次大值一定是小于 \(k\) 的,所以下传时只需要讨论前两种,而单次复杂度 \(O(1)\)。

点击查看代码
struct SegmentTree{
    #define mid ((l+r)>>1)
    #define lson rt<<1,l,mid
    #define rson rt<<1|1,mid+1,r
    #define ls rt<<1
    #define rs rt<<1|1
    ll sum[maxn<<2];
    int mx[maxn<<2],cnt[maxn<<2],sec[maxn<<2];
    int tag[maxn<<2];
    inline void push_up(int rt){
        sum[rt]=(sum[ls]+sum[rs]);
        if(mx[ls]==mx[rs]){
            mx[rt]=mx[ls],cnt[rt]=cnt[ls]+cnt[rs];
            sec[rt]=max(sec[ls],sec[rs]);
        }
        else if(mx[ls]>mx[rs]){
            mx[rt]=mx[ls],cnt[rt]=cnt[ls];
            sec[rt]=max(sec[ls],mx[rs]);
        }
        else{
            mx[rt]=mx[rs],cnt[rt]=cnt[rs];
            sec[rt]=max(sec[rs],mx[ls]);
        }
    }
    inline void push_tag(int rt,int k){
        if(mx[rt]<=k) return;
        else{
            sum[rt]-=1ll*cnt[rt]*(mx[rt]-k);
            mx[rt]=k,tag[rt]=k;
        }
    }
    inline void push_down(int rt){
        if(tag[rt]!=-1){
            push_tag(rt<<1,tag[rt]);
            push_tag(rt<<1|1,tag[rt]);
            tag[rt]=-1;
        }  
    }
    void build(int rt,int l,int r){
        tag[rt]=-1;
        if(l==r){
            sum[rt]=a[l],mx[rt]=a[l],cnt[rt]=1,sec[rt]=-1;
            return;
        }
        build(lson),build(rson);
        push_up(rt);
    }
    void update(int rt,int l,int r,int pl,int pr,int k){
        if(pl<=l&&r<=pr&&sec[rt]<k) return push_tag(rt,k),void();
        push_down(rt);
        if(pl<=mid) update(lson,pl,pr,k);
        if(pr>mid) update(rson,pl,pr,k);
        push_up(rt);
    }
    ll query_sum(int rt,int l,int r,int pl,int pr){
        if(pl<=l&&r<=pr) return sum[rt];
        push_down(rt);
        ll res=0;
        if(pl<=mid) res+=query_sum(lson,pl,pr);
        if(pr>mid) res+=query_sum(rson,pl,pr);
        return res; 
    }
    int query_max(int rt,int l,int r,int pl,int pr){
        if(pl<=l&&r<=pr) return mx[rt];
        push_down(rt);
        int res=-1;
        if(pl<=mid) res=max(res,query_max(lson,pl,pr));
        if(pr>mid) res=max(res,query_max(rson,pl,pr));
        return res;
    }
    #undef mid
    #undef lson
    #undef rson
    #undef ls
    #undef rs
}S;

时间复杂度证明

吉老师的论文中的证明如下。

将最大值设为一种标记,并去掉所有与父亲节点标记相同的标记,这样标记只存在于 \(O(n)\) 个位置,且区间次大值等价于一个节点子树(不包括本身)中的最大标记。

定义一次取 \(\min\) 操作及其延迟下传产生的标记为一类标记。定义一类标记的权值为其存在的所有节点与根节点组成的虚树大小,设势能函数 \(\Phi(x)\) 为所有标记的总权值。

区间取 \(\min\) 操作直接影响到的节点数为 \(O(\log n)\),也使每影响到一个节点便势能函数增加 \(O(1)\)。

下传标记的操作是伴随其他操作进行的,且每次下传使势能函数增加 \(O(1)\)。

最重要的部分是暴力向下走的情况,此时暴力向下走说明修改值 \(k\) 是小于次小值的,也就是说子树内一定有小于修改值 \(k\) 的标记,而每次到一个节点便会使这个标记减小,即使这一类标记权值减小 \(O(1)\)。

因此每种操作都会以 \(O(1)\) 的复杂度使势能函数产生 \(O(1)\) 的变化,而势能函数的增加量是 \(O(q\log n)\) 的,减少量也不会超过 \(O(q\log n)\),于是复杂度就是 \(O(q\log n)\) 的。

参考资料

  • 《2016国家集训队论文集:区间最值操作与历史最值问题》- 吉如一

标签:rt,log,标记,int,复杂度,Tree,笔记,Beats,节点
From: https://www.cnblogs.com/SoyTony/p/Learning_Notes_about_Segment_Tree_Beats.html

相关文章

  • 谷粒学院day01-06回顾笔记
    reviewday01-06day01day02day03day04day05day06......
  • python 的 Type Hint 类型标注学习笔记
    学习笔记,用于本人忘记知识点时回顾。int在变量后加int即可声明该变量为int类型,当调用该函数时,如果填入的参数不为int类型,则报错。函数名后加->int声明该函数......
  • 《分布式技术原理与算法解析》学习笔记Day23
    分布式数据复制我们在进行分布式数据存储设计时,通常会考虑对数据进行备份,以提高数据的可用性和可靠性,“数据复制技术”就是实现数据备份的关键技术。什么是数据复制技术?......
  • Python笔记--练习题(都来瞧一瞧,看一看嘞)
    利用Python对文件进行操作重新写入的文件如下图所示:统计学生成绩文件的最高分最低分和平均分Python如何统计英文文章出现最多的单词Python统计目录下的文件大小......
  • 《密码工程》第1、2章学习笔记
    《密码工程》第1、2章学习笔记第1章密码学研究范围思维导图知识概述1.1密码学作用密码学本身没有价值,必须作为一个系统的一部分,以起到相应的作用。我们可以把密码学......
  • 数论基础笔记
    数论基础DanBoneh课程中数论基础笔记,该部分内容用于构建以下密码学相关部分:KeyExchangeProtocolsDigitalSignaturesPublic-KeyEncryption目录数论基础Notation......
  • 考研算法辅导课笔记:第十七讲--枚举和位运算
    这节课主要讲枚举,位运算成员函数booloperator<(constRange&b)const{};括号中的const表示参数b对象不会被修改;在函数末尾加CONST这样的函数叫常成员函数。常成员函......
  • ECharts笔记--实现地图版的数据显示(存在bug/┭┮﹏┭┮/)
    相关描述前几天实现了柱状图等图的数据可视化,现在就来接着实现一下“更加”形象的数据可视化吧!具体实现如下<%@taglibprefix="c"uri="http://java.sun.com/jsp/jstl/......
  • 读Java性能权威指南(第2版)笔记02_ Java SE API技巧上
    1. 压缩字符串1.1. Java61.2. 实验性1.3. compressedstring2. 字符串2.1. Java82.2. 所有都会编码为16位字符数组3. 紧凑字符串3.1. Java113.2. c......
  • ssm学习笔记23001-mybatis基础查询
    myBatis的基础查询单条或多条查询根据id查询单条数据,查询所有数据的列表集合,查询所有数据的条目,查询出单条数据返回值为map,查询多条数据返回值为列表,查询多条数据返回值......