0.简介

zkw线段树是一种非递归线段树,与普通线段树不同的是,它是棵标准的满二叉树,所以遍历过程可全程使用位运算,常数一般比线段树小得多。

1.结构/建树

前面说了,zkw线段树是满二叉树,可是原数组大小不一定是2^n,所以我们就多开一些废点,硬充,另外,它需要左右各两个点当哨兵,原因看下面的查询原理就知道了。

有人会想,开一些废点是不是空间会更大,相反,一般线段树由于相比之下结构混乱,一般开4倍,而zkw只用开3倍。

这就是zkw:

在zkw线段树中,最下面一排的点就是原数组上的点,因为是满二叉树,所以这些点的深度都一样,设为d(1的深度为0,图中d为4),然后会发现底排上面的点(从1~15)数量刚好为2^d-1,原数组中的元素编号(最底下红色字体)对应的线段树上的点的编号恰好增加了2^d,最低排点数也为2^d(包括废点),那么我们只需要知道这个常数d,就可以确定这棵树的结构了,所以建树很简单,只需要计算最小的、使最低排点数足够的d,实际上,计算2^d后面更方便:

int M; //M 即 2^d
void maketree(int n) {//原数组长度
M = 1; while(M < n+2/*包括哨兵*/) M <<= 1;
}

2.单点修改

我们知道了2^d,就可以直接求出原数组中某个元素在线段树上的点,然后一路向上更新(编号/2下取整即为其父亲的编号):

void addtree(int x,int y) {//y的类型视情况而变
int s = M + x;
tre[s] = y; s >>= 1;
while(s) {
tre[s] = Merge(tre[s<<1],tre[s<<1|1]);//自定义的某种合并操作,可以是相加、相乘、最大值最小值等
s >>= 1;
}
}

3.区间查询

当我们知道要查询的区间[l,r]时,就把区间两端往外的两个点记录为s(M+(l-1)),t(M+(r+1)),那么两点到 lca 路径包围住的点就恰好是要查询的区间,把s和t往父亲方向走,每当s为其父亲的左儿子(s为偶数),那它的兄弟一定在区间里(s ^ 1),每当t为其父亲的右儿子(t为奇数),那它的兄弟也一定在区间里(t ^ 1):

int findtree(int l,int r) {
int s = M + l - 1,t = M + r + 1;
int ans = 0;
while(s || t) {
if((s>>1) ^ (t>>1)) { // s/2 != t/2
if(!(s & 1)) ans = Merge(ans,tre[s ^ 1]);
if(t & 1) ans = Merge(ans,tre[t ^ 1]);
}
else break;
s >>= 1;t >>= 1;
}
return ans;
}

4.永久化懒标记

zkw线段树的修改和查询都是从下往上做,因此懒标记不方便往下放,于是就用永久化的懒标记,记录整个子树的修改(不包括子树的根),以后每次查询都要按照深度依次修改答案。

用懒标记就可以支持不按时间顺序修改的区间修改了,和不用懒标记的区间查询差不多,但是要修改访问到的区间内点的懒标记,并且更新经过的所有节点,也就是说不能中途break了。

以区间最大值为例:

void addtree(int l,int r,int y) { //区间修改
int s = M + l - 1,r = M + r + 1;
while(s || t) {
if(s < M) tre[s] = max(tre[s<<1],tre[s<<1|1]) + lz[s]; //lz[]为懒标记数组
if(t < M) tre[t] = max(tre[t<<1],tre[t<<1|1]) + lz[t]; //路途中的节点修改,但不包括最下面一排,因为它们没有子节点
if((s>>1) ^ (t>>1)) {
if(!(s & 1)) tre[s^1] += y,lz[s^1] += y;
if(t & 1) tre[t^1] += y,lz[t^1] += y;
} //不能break
s >>= 1;t >>= 1;
}
}
void findtree(int l,int r) { //区间查询
int s = M + l - 1,r = M + r + 1;
int ls = 0,rs = 0; //左右两边单独算,因为左边每一个父亲只能照顾左边路径上的子孙,右边同理
while(s || t) {
ls += lz[s];
rs += lz[t];
if((s>>1) ^ (t>>1)) {
if(!(s & 1)) ls = max(ls,tre[s^1]); //这里不能考虑懒标记,因为懒标记省略的修改不包括自己
if(t & 1) rs = max(rs,tre[t^1]); //当然,这只是笔者个人的写法,看官随意
}
s >>= 1;t >>= 1;
}
return max(ls,rs);
}

5.其他

除了这些操作,zkw还可以支持线段树二分,只不过只有到最底层的时候,才能知道具体的位置。

记录每个点的具体区间范围、长度,甚至可以将就打递归版。

此外,zkw线段树结构的性质还可以运用到其他题目上。

zkw线段树——简单易懂好写好调的线段树的更多相关文章

  1. 迅为4412开发板Linux设备树的镜像烧写和源码简单优化教程

    1 烧写:   烧写和4412默认镜像的烧写类似,使用fastboot. 先更新uboot,用4412默认uboot更新支持设备树的uboot 用支持设备树的uboot烧写. 进入支持设备树的uboo ...

  2. 线段树简单入门 (含普通线段树, zkw线段树, 主席树)

    线段树简单入门 递归版线段树 线段树的定义 线段树, 顾名思义, 就是每个节点表示一个区间. 线段树通常维护一些区间的值, 例如区间和. 比如, 上图 \([2, 5]\) 区间的和, 为以下区间的和 ...

  3. UOJ#400. 【CTSC2018】暴力写挂 边分治 线段树合并

    原文链接 www.cnblogs.com/zhouzhendong/p/UOJ400.html 前言 老年选手没有码力. 题解 先对第一棵树进行边分治,然后,设点 x 到分治中心的距离为 $D[x]$ ...

  4. 【repost】让你一句话理解闭包(简单易懂)

    接触javascript很久了,每次理解闭包都似是而非,最近在找Web前端的工作,所以需要把基础夯实一下. 本文是参照了joy_lee的博客 闭包 在她这篇博客的基础上以批注的形式力争把我的理解阐述出 ...

  5. UE4中的AI行为树简单介绍

    UE4引擎中可以实现简单AI的方式有很多,行为树是其中比较常用也很实用的AI控制方式,在官网的学习文档中也有最简单的目标跟踪AI操作教程,笔者在这里只作简单介绍. AIController->和 ...

  6. 【转】JS回调函数--简单易懂有实例

    JS回调函数--简单易懂有实例 初学js的时候,被回调函数搞得很晕,现在回过头来总结一下什么是回调函数. 我们先来看看回调的英文定义:A callback is a function that is ...

  7. java生成RSA公私钥字符串,简单易懂

    java生成RSA公私钥字符串,简单易懂   解决方法: 1.下载bcprov-jdk16-140.jar包,参考:http://www.yayihouse.com/yayishuwu/chapter ...

  8. HashSet的实现原理,简单易懂

    HashSet的实现原理,简单易懂   答: HashSet实际上是一个HashMap实例,都是一个存放链表的数组.它不保证存储元素的迭代顺序:此类允许使用null元素.HashSet中不允许有重复元 ...

  9. 用最简单的代码写出banner图轮播效果

    以下视频是由[赵一鸣随笔]博客提供的“用最简单的代码写出banner图轮播效果”. 查看全屏高清视频,请点击链接:http://www.zymseo.com/58.html

随机推荐

  1. jupyter notebook修改默认浏览器

    1. anaconda集成了python以及各种库.python和anaconda可二选一. 2. anaconda或python安装后记得把pip源改为国内的镜像源地址.比如163,阿里,清华以及南 ...

  2. ExtJS直接加载HTML页面

    ExtJS直接加载HTML页面 说明 ExtJS组件很不错,但再完美也有需要其他组件的时候,比如有时候就需要引入已经写好的HTML页面.主要的方法如下. 测试环境:ExtJS 7.4 使用html配置 ...

  3. SAP 文件操作类 CL_GUI_FRONTEND_SERVICES

    1 .文件下载. DATA: l_filename TYPE string, "file name l_path TYPE string, "file path l_fullpat ...

  4. 基于bat脚本的前端发布流程的优化

    背景介绍 前面在基于bat脚本的前端发布流程设计与实现中,我已经介绍了设计与实现,这一篇主要是针对其的一个优化折腾(分两步走,第一步先搞出来,第二步再想着怎么去优化它),我主要做了以下几件事. &qu ...

  5. Redis开机自启

    添加开机启动服务 vi /etc/systemd/system/redis.service 在redis.service中输入以下内容 ps:ExecStart配置成自己的路径 [Unit] Desc ...

  6. python测试开发django-197.django-celery-beat 定时任务

    前言 django-celery-beat 可以支持定时任务,把定时任务写到数据库. 接着前面这篇写python测试开发django-196.python3.8+django2+celery5.2.7 ...

  7. 4-11 CS后台项目-4 及 Redis缓存数据

    使用Redis缓存数据 使用Redis可以提高查询效率,一定程度上可以减轻数据库服务器的压力,从而保护了数据库. 通常,应用Redis的场景有: 高频查询,例如:热搜列表.秒杀 改变频率低的数据,例如 ...

  8. 工作流引擎在vivo营销自动化中的应用实践 | 引擎篇03

    作者:vivo 互联网服务器团队- Cheng Wangrong 本文是<vivo营销自动化技术解密>的第4篇文章,分析了在营销自动化业务引入工作流技术的背景和工作流引擎的介绍,同时介绍了 ...

  9. maven exclusion 理解

    结论:exclusion 表示对传递性依赖进行排除,排除后当前项目的依赖jar中,就不会包含该传递性依赖. 扩展:项目中的jar 都会在classpath下,排除后的传递性依赖,相当于在classpa ...

  10. Ubu18远程登录密匙设置

    Ubu18设置远程密匙登录 相关文件 /etc/ssh/sshd_config 注意vscode使用博客园插件需要进行端口转发,在vscode端口处设置41385 本地生成密匙,任选一种,这里只介绍第 ...