线段树建造

1、由于二叉树的自身特性,对于每个父亲节点的编号 i,他的两个儿子的编号分别是 2i 和 2i+1,所以我们考虑写两个 O(1) 的取儿子函数:

inline int ls(int p){return p<<1;} 左儿子 inline int rs(int p){return p<<1|1;} 右儿子 < code></1;}>

二进制位左移一位代表着数值 X 2,而如果左移完之后再或上 1,由于左移完之后最后一位二进制位上一定会是 0 ,所以 |1 等价于 +1 。这个地方更多是为了方便,速度方面理论上是要比 +1 快,但其实编译器会帮你主动干这件事。

2、 那么根据线段树的服务对象,可以得到线段树的维护:

void push_up_sum(int p){
    t[p]=t[lc(p)]+t[rc(p)];
}//   &#x5411;&#x4E0A;&#x4E0D;&#x65AD;&#x7EF4;&#x62A4;&#x533A;&#x95F4;&#x64CD;&#x4F5C;

void push_up_min(int p){//max and min
     t[p]=min(t[lc(p)],t[rc(p)]);
     //t[p]=max(t[lc(p)],t[rc(p)]);
}

此处一定要注意,push up 操作的目的是为了维护父子节点之间的逻辑关系。当我们递归建树时,对于每一个节点我们都需要遍历一遍,并且电脑中的递归实际意义是先向底层递归,然后从底层向上回溯,所以开始递归之后必然是先去整合子节点的信息,再向它们的祖先回溯整合之后的信息。(这其实是正确性的证明啦)。

呐,我们在这儿就能看出来,实际上 push_up 是在合并两个子节点的信息,所以需要信息满足结合律!

3、 那么对于建树,由于二叉树自身的父子节点之间的可传递关系,所以可以考虑递归建树 ,并且在建树的同时,我们应该维护父子节点的关系:

int n;
int ans[MAXN*4];

void build(ll p,ll l,ll r){//p&#x4E3A;&#x6839;&#x8282;&#x70B9;&#xFF0C;l&#x4E3A;&#x533A;&#x95F4;&#x5F00;&#x59CB;&#x8282;&#x70B9;&#xFF0C;r&#x4E3A;&#x533A;&#x95F4;&#x7ED3;&#x675F;&#x8282;&#x70B9;
  if(l==r){ans[p]=a[l];return ;}
  //&#x5982;&#x679C;&#x5DE6;&#x53F3;&#x533A;&#x95F4;&#x76F8;&#x540C;&#xFF0C;&#x90A3;&#x4E48;&#x5FC5;&#x7136;&#x662F;&#x53F6;&#x5B50;&#x8282;&#x70B9;&#x5566;&#xFF0C;&#x53EA;&#x6709;&#x53F6;&#x5B50;&#x8282;&#x70B9;&#x662F;&#x88AB;&#x771F;&#x5B9E;&#x8D4B;&#x503C;&#x7684;
  ll mid=(l+r)>>1;
  build(ls(p),l,mid);
  build(rs(p),mid+1,r);
//&#x6B64;&#x5904;&#x7531;&#x4E8E;&#x6211;&#x4EEC;&#x91C7;&#x7528;&#x7684;&#x662F;&#x4E8C;&#x53C9;&#x6811;&#xFF0C;&#x6240;&#x4EE5;&#x5BF9;&#x4E8E;&#x6574;&#x4E2A;&#x7ED3;&#x6784;&#x6765;&#x8BF4;&#xFF0C;&#x53EF;&#x4EE5;&#x7528;&#x4E8C;&#x5206;&#x6765;&#x964D;&#x4F4E;&#x590D;&#x6742;&#x5EA6;&#xFF0C;&#x5426;&#x5219;&#x6811;&#x5F62;&#x7ED3;&#x6784;&#x5219;&#x6CA1;&#x6709;&#x4EC0;&#x4E48;&#x660E;&#x663E;&#x7684;&#x4F18;&#x5316;
  push_up(p);
//&#x6B64;&#x5904;&#x7531;&#x4E8E;&#x6211;&#x4EEC;&#x662F;&#x8981;&#x901A;&#x8FC7;&#x5B50;&#x8282;&#x70B9;&#x6765;&#x7EF4;&#x62A4;&#x7236;&#x4EB2;&#x8282;&#x70B9;&#xFF0C;&#x6240;&#x4EE5;pushup&#x7684;&#x4F4D;&#x7F6E;&#x5E94;&#x5F53;&#x662F;&#x5728;&#x56DE;&#x6EAF;&#x65F6;&#x3002;&#x6B64;&#x65F6;&#x5B50;&#x8282;&#x70B9;&#x7684;&#x6570;&#x636E;&#x5DF2;&#x7ECF;&#x77E5;&#x9053;&#x3002;
}

那么对于区间操作,我们考虑引入一个名叫” lazy~tag”(懒标记)的东西——之所以称其”lazy”,是因为原本区间修改需要通过先改变叶子节点的值,然后不断地向上递归修改祖先节点直至到达根节点,时间复杂度最高可以到达 O(nlogn) 的级别。但当我们引入了懒标记之后,区间更新的期望复杂度就降到了 O(logn) 的级别且甚至会更低.

1、首先先来从分块思想上解释如何区间修改:
分块的思想是通过将整个序列分为有穷个小块,对于要查询的一段区间,总是可以整合成 k 个所分块与 m 个单个元素的信息的并

那么我们可以反过来思考这个问题:对于一个要修改的、长度为 l 的区间来说,总是可以看做由一个长度为2^log(n)和剩下的元素(或者小区间组成)。

那么我们就可以先将其拆分成线段树上节点所示的区间,之后分开处理:

(如果单个元素被包含就只改变自己,如果整个区间被包含就修改整个区间。)

其实好像这个在分块里不是特别简单地实现,但是在线段树里,无论是元素还是区间都是线段树上的一个节点,所以我们不需要区分区间还是元素,加个判断就好。

2、懒标记的正确打开方式

首先,懒标记的作用是记录每次、每个节点要更新的值,也就是 Δ。但线段树的优点不在于全记录(全记录依然很慢 qwq),而在于传递式记录:
整个区间都被操作,记录在公共祖先节点上;只修改了一部分,那么就记录在这部分的公共祖先上;如果四环以内只修改了自己的话,那就只改变自己。

After that,如果我们采用上述的优化方式的话,我们就需要在每次区间的查询修改时 push_down 一次,以免重复或者冲突或者爆炸
那么对于 push_down 而言,其实就是纯粹的 push_up 的逆向思维(但不是逆向操作): 因为修改信息存在父节点上,所以要由父节点向下传导 lazy~tag 。

那么问题来了:怎么传导 push_down 呢?这里很有意思,开始回溯时执行 push_up,因为是向上传导信息;那我们如果要让它向下更新,就调整顺序,在向下递归的时候 push_down 不就好惹~ qwq:

inline void f(ll p,ll l,ll r,ll k){
   tag[p]=tag[p]+k;
   ans[p]=ans[p]+k*(r-l+1);
   //&#x7531;&#x4E8E;&#x662F;&#x8FD9;&#x4E2A;&#x533A;&#x95F4;&#x7EDF;&#x4E00;&#x6539;&#x53D8;&#xFF0C;&#x6240;&#x4EE5;ans&#x6570;&#x7EC4;&#x8981;&#x52A0;&#x5143;&#x7D20;&#x4E2A;&#x6570;&#x6B21;&#x5566;
}
//&#x6211;&#x4EEC;&#x53EF;&#x4EE5;&#x8BA4;&#x8BC6;&#x5230;&#xFF0C;f&#x51FD;&#x6570;&#x7684;&#x552F;&#x4E00;&#x76EE;&#x7684;&#xFF0C;&#x5C31;&#x662F;&#x8BB0;&#x5F55;&#x5F53;&#x524D;&#x8282;&#x70B9;&#x6240;&#x4EE3;&#x8868;&#x7684;&#x533A;&#x95F4;
inline void push_down(ll p,ll l,ll r){
   ll mid=(l+r)>>1;
   f(ls(p),l,mid,tag[p]);
   f(rs(p),mid+1,r,tag[p]);
   tag[p]=0;
   //&#x6BCF;&#x6B21;&#x66F4;&#x65B0;&#x4E24;&#x4E2A;&#x513F;&#x5B50;&#x8282;&#x70B9;&#x3002;&#x4EE5;&#x6B64;&#x4E0D;&#x65AD;&#x5411;&#x4E0B;&#x4F20;&#x9012;
}
inline void update(ll nl,ll nr,ll l,ll r,ll p,ll k){
   //nl,nr&#x4E3A;&#x8981;&#x4FEE;&#x6539;&#x7684;&#x533A;&#x95F4;
   //l,r,p&#x4E3A;&#x5F53;&#x524D;&#x8282;&#x70B9;&#x6240;&#x5B58;&#x50A8;&#x7684;&#x533A;&#x95F4;&#x4EE5;&#x53CA;&#x8282;&#x70B9;&#x7684;&#x7F16;&#x53F7;
   if(nl<=l&&r<=nr) { ans[p]+="k*(r-l+1);" tag[p]+="k;" return ; } push_down(p,l,r); 回溯之前(也可以说是下一次递归之前,因为没有递归就没有回溯) 由于是在回溯之前不断向下传递,所以自然每个节点都可以更新到 ll mid="(l+r)">>1;
   if(nl<=mid)update(nl,nr,l,mid,ls(p),k); if(nr>mid) update(nl,nr,mid+1,r,rs(p),k);
   push_up(p);
   //&#x56DE;&#x6EAF;&#x4E4B;&#x540E;
}
</=mid)update(nl,nr,l,mid,ls(p),k);></=l&&r<=nr)>

代码如下

ll query(ll q_x,ll q_y,ll l,ll r,ll p){
        ll res=0;
        if(q_x<=l&&r<=q_y)return ans[p]; ll mid="(l+r)">>1;
        push_down(p,l,r);
        if(q_x<=mid)res+=query(q_x,q_y,l,mid,ls(p)); if(q_y>mid) res+=query(q_x,q_y,mid+1,r,rs(p));
        return res;
}
</=mid)res+=query(q_x,q_y,l,mid,ls(p));></=l&&r<=q_y)return>

这是所有的函数:

unsigned ll n,m,a[MAXN],ans[MAXN<<2],tag[maxn<<2]; tag数组为标记 inline ll ls(ll x) { return x<<1; } rs(ll x<<1|1; void push_up(ll p) ans[p]="ans[ls(p)]+ans[rs(p)];" build(ll p,ll l,ll r) tag[p]="0;" if(l="=r){ans[p]=a[l];return" ;} mid="(l+r)">>1;
    build(ls(p),l,mid);
    build(rs(p),mid+1,r);
    push_up(p);
}

inline void f(ll p,ll l,ll r,ll k)
{
    tag[p]=tag[p]+k;
    ans[p]=ans[p]+k*(r-l+1);
}

inline void push_down(ll p,ll l,ll r)
{
    ll mid=(l+r)>>1;
    f(ls(p),l,mid,tag[p]);
    f(rs(p),mid+1,r,tag[p]);
    tag[p]=0;
}

inline void update(ll nl,ll nr,ll l,ll r,ll p,ll k)
{
    if(nl<=l&&r<=nr) { ans[p]+="k*(r-l+1);" tag[p]+="k;" return ; } push_down(p,l,r); ll mid="(l+r)">>1;
    if(nl<=mid)update(nl,nr,l,mid,ls(p),k); if(nr>mid) update(nl,nr,mid+1,r,rs(p),k);
    push_up(p);
}

ll query(ll q_x,ll q_y,ll l,ll r,ll p)
{
    ll res=0;
    if(q_x<=l&&r<=q_y)return ans[p]; ll mid="(l+r)">>1;
    push_down(p,l,r);
    if(q_x<=mid)res+=query(q_x,q_y,l,mid,ls(p)); if(q_y>mid) res+=query(q_x,q_y,mid+1,r,rs(p));
    return res;
}
</=mid)res+=query(q_x,q_y,l,mid,ls(p));></=l&&r<=q_y)return></=mid)update(nl,nr,l,mid,ls(p),k);></=l&&r<=nr)></2],tag[maxn<<2];>

Original: https://www.cnblogs.com/aska00/p/16524813.html
Author: Aska0
Title: 线段树建造

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/686051/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • HashMap详解

    什么是HashMap容器 【1】HashMap是使用频率最高的用于映射(键值对)处理的数据类型。随着JDK(Java Developmet Kit)版本的更新,JDK1.8对Has…

    技术杂谈 2023年7月24日
    080
  • Vim使用技巧(持续更新)

    好记性不如烂笔头,在这里记录一些Vim使用技巧 vim配置 "&#x62F7;&#x8D1D;&#x540C;&#x6B65;&#…

    技术杂谈 2023年6月21日
    096
  • ueditor实现ctrl+v粘贴word图片并上传

    图片的复制无非有两种方法,一种是图片直接上传到服务器,另外一种转换成二进制流的base64码目前限chrome浏览器使用首先以um-editor的二进制流保存为例:打开umedit…

    技术杂谈 2023年5月31日
    098
  • 数据库多表查询 联合查询 增删改查

    插入 方式一 语法: insert into 表名 (字段名,…) values (值,…); 特点: 1、要求值的类型和字段的类型要一致或兼容 2、字段的个数和顺序不一定…

    技术杂谈 2023年6月21日
    084
  • mybatis学习笔记(一)for 概念

    mybaits相关概念 1.1 mybatis简介 mybatis是是一款优秀的基于ORM的半自动轻量级持久层框架,它支持定制化SQL、存储过程以及高级映射。(与另一基于ORM的持…

    技术杂谈 2023年7月11日
    064
  • 项目中所用到的mysql重复过滤

    问题:首先用户会本地上传一批号码(可能重复)到我们项目,通过解析文件,把号码入库(只验证是不是号码其他不做改动)到号码表,然后对号码进行去重操作. 表结构为:主键(id),号码(m…

    技术杂谈 2023年7月23日
    085
  • MySQL高可用安装

    MySQL HA部署 环境准备 创建本地yum源 确认关闭 SELinux 防火墙设置 MySQL安装 使用 root 用户操作创建相关的用户组和用户 上传/解压介质 设置自启动 …

    技术杂谈 2023年7月24日
    076
  • 最左前缀有手就会,那索引下推呢?

    联合索引的最左前缀原则属于面试高频题,想必大部分同学都知道一些,但是,那些不符合最左前缀的部分,会怎么样呢(索引下推) 索引下推不算高频题,知道的同学应该不是很多(不过并不代表有啥…

    技术杂谈 2023年7月25日
    0241
  • 010 Linux 文本统计与去重 (wc 和 uniq)

    wc 命令一般是作为组合命令的一员与其他命令一同起到统计的作用。而一般情况下使用wc -l 命令较多。uniq 可检查文本文件中重复出现的行,一般与 sort 命令结合使用。一起组…

    技术杂谈 2023年7月10日
    089
  • java反序列化_link_six

    0x01前言 经过cc链一的学习,然后jdk的版本一更新那两条链子就不能用了,然后这种反序列化的话就很不不止依赖于cc包的引入还有jdk版本,于是就出现了cc_link_six一个…

    技术杂谈 2023年6月21日
    064
  • 新接口开发-工时计算

    博客园 :当前访问的博文已被密码保护 请输入阅读密码: Original: https://www.cnblogs.com/shoshana-kong/p/16498596.htm…

    技术杂谈 2023年6月1日
    089
  • centos 7 安装KVM

    一、安装KVM 实验环境如下: 虚拟机版本:VMware 12.5.7虚拟机需要开启虚拟化,如下图: 系统版本:CentOS Linux release 7.5.1804 (Cor…

    技术杂谈 2023年7月10日
    097
  • 容器不能使用 ps 等命令了 如何处理

    404. 抱歉,您访问的资源不存在。 可能是网址有误,或者对应的内容被删除,或者处于私有状态。 代码改变世界,联系邮箱 contact@cnblogs.com 园子的商业化努力-困…

    技术杂谈 2023年5月31日
    0105
  • 创建PO提示错误:计税时出错。请解决此问题或与您的系统管理员联系。

    问题:创建PO提示错误:计税时出错。请解决此问题或与您的系统管理员联系。 解决方法:维护汇率和打开期间 Original: https://www.cnblogs.com/quan…

    技术杂谈 2023年6月1日
    0128
  • hdu2068RPG的错排

    Problem Description 今年暑假杭电ACM集训队第一次组成女生队,其中有一队叫RPG,但做为集训队成员之一的野骆驼竟然不知道RPG三个人具体是谁谁。RPG给他机会让…

    技术杂谈 2023年5月31日
    089
  • 【证券从业】金融基础知识-第四章 股票01

    注1:后续学习并整理到第八章,全书完结后再合并成一个笔记进行源文件分享 注2:本章内容巨多,大约分为三篇文章记录消化 posted @2022-06-05 00:25 陈景中 阅读…

    技术杂谈 2023年7月10日
    077
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球