非递归式(zkw)线段树详解(一)

2023-10-07 03:30
文章标签 详解 递归 线段 zkw

本文主要是介绍非递归式(zkw)线段树详解(一),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

线段树,是在信息学各类比赛中经常出现的数据结构之一
普通的线段树大家应该都会,下面来介绍一种不需要递归的线段树

zkw线段树
出自:《统计的力量》-张昆玮

zkw线段树不同于普通线段树的地方在于:它采用堆结构,构造一颗满二叉树(也可以说是完全二叉树),而二叉树的最后一层则是各个节点。
这里写图片描述
注意:zkw线段树必须是点树,即完全闭区间
普通线段树中的修改需要去查询节点,并分为三类:
完全覆盖,在左区间,在右区间
那么换个方法,自底向上更新呢?
这就是zkw的想法

下面以 单点修改 区间求值为例子
建树:
采用堆结构,取到log(n)-1层最后一个节点,例如上图中,[3,4]节点的标号为3。
那么从3+1开始到3+4,就是1-4四个点的位置了。
好。我们直接把值丢到tree[4~7]中就可以了。。。。
同时向上更新:tree[i]=tree[i*2]+tree[i*2+1]
代码:

inline void up(int x)
{tr[x]=tr[x<<1]+tr[x<<1|1];
}
inline void build()
{for(M=1;M<=n+1;M<<=1);for(int j=M+1;j<=M+n;j++)scanf("%d",&tr[j]);for(int j=M-1;j;j--)up(j);
}

上面代码中,M为log(n)-1层的最后一个节点的下标

更新:
单点更新,更新第k个点同时更新所有k的祖先

inline void update(int x,int y)
{for(tr[x+=M]+=y,x>>=1;x;x>>=1)up(x);
}

查询:
查询s-t区间的和
这个略微复杂,考虑s和t的区间位置:
当s为左儿子,则s的父亲节点被s-t包含,反之该区间内只有s被包含同理 当t为右儿子,则t的父亲节点被s-t包含
很容易理解 可以直接加上t和s的兄弟节点来得到父亲节点的值,同时把s和t上移
当s和t是兄弟时,说明s-t的区间中所有数已经被全部包含
这里可以用到一些位运算优化:
用&确定左右子树,用^确定是否为兄弟关系

inline int  query(int s,int t)
{int ans=0;s=s+M-1;t=t+M+1;for(;s^t^1;s>>=1,t>>=1){if(~s&1)ans+=tr[s^1];if(t&1) ans+=tr[t^1];}return ans;
}

然后。。整个代码就写完了

#include<cstdio>
#include<iostream>
#include<cmath>
using namespace std;
int M;
int tr[1500001],n,m;
inline void up(int x)
{tr[x]=tr[x<<1]+tr[x<<1|1];
}
inline void build()
{for(M=1;M<=n+1;M<<=1);for(int j=M+1;j<=M+n;j++){int t;scanf("%d",&t);tr[j]=t;}for(int j=M-1;j;j--)up(j);
}
inline void update(int x,int y)
{for(tr[x+=M]+=y,x>>=1;x;x>>=1)up(x);
}
inline int  query(int s,int t)
{int ans=0;s=s+M-1;t=t+M+1;for(;s^t^1;s>>=1,t>>=1){if(~s&1)ans+=tr[s^1];if(t&1) ans+=tr[t^1];}return ans;
}
int main()
{cin>>n>>m;build();for(int i=1;i<=m;i++){int x,y,z;scanf("%d%d%d",&x,&y,&z);if(x==1)update(y,z);else    printf("%d\n",query(y,z));}
}

是不是比普通线段树的代码简短好多。。同时运行速度比普通线段树快2-3倍



接下来…..恶心的来了

区间修改
原文中说是要利用的。。前缀的前缀和。。。
本蒟蒻语文不好。。是在不懂什么意思- -
但是大概是利用差分思想(不是积分差分的那个

一个简单的例子:区间修改 单点求值
(区间修改 区间求和下次再说咯。。
我们可以发现,在这个问题中,如果一个个单点修改的复杂度是nlogn,好像还不如暴力。。
大家如果学过树状数组,应该好理解一些。
我们可以把每个点的值,化为从根节点到那个点的链上所有标记之和
上面说了标记。。没错。。类似于lazy标记
但是这个lazy标记不会被pushdown反而只会upupup
考虑这样的情况

这里写图片描述
求链上的和,我们就可以把这棵树改造为
这里写图片描述
上述操作代码:

int mi=min(tr[s],tr[s^1]);
tr[s]-=mi;
tr[s^1]-=mi;
tr[s>>1]+=mi;
s>>=1;

那么这样,我们就可以只记录它的偏移量,而不直接记录值
于是就构造了一颗。。标记树
对于单点求和,确实没有必要记录下每个点的值,但是如果要区间求值就需要建两棵zkw树了。。比较烦,下次讲
那么求单点的值也是很简单的啦。下面来分步讲解
建树:
同上,只是更新1-M之间的过程改一下

inline void build()
{for(M=1;M<=n+1;M<<=1);for(int i=M+1;i<=M+n;i++)scanf("%d",&tr[i]);int t=M+n;for(t=M;t;t--){int mi=min(tr[t<<1],tr[t<<1|1]);tr[t<<1]-=mi;tr[t<<1|1]-=mi;tr[t]+=mi;}
}

更新:
由于要处理区间,可以参考之前的求区间和部分程序
只不过要加上那个up,并且求值改为修改

inline void add(int s,int t,int v)
{for(s=s+M-1,t=t+M+1;s^t^1;s>>=1,t>>=1){if(~s&1)   tr[s^1]+=v;if(t&1)     tr[t^1]+=v;int mi=min(tr[s^1],tr[s]);tr[s]-=mi;tr[s^1]-=mi;tr[s>>1]+=mi;mi=min(tr[t^1],tr[t]);tr[t]-=mi;tr[t^1]-=mi;tr[t>>1]+=mi;}while(s){int mi=min(tr[s],tr[s^1]);tr[s]-=mi;tr[s^1]-=mi;tr[s>>1]+=mi;s>>=1;}
}

好吧我知道这个有点恶心。。但是仔细看还是容易理解的
查询:
这个简单了啊
一条链上全部加起来

inline int query(int x)
{int sum=0,p=M+x;while(p)sum+=tr[p],p>>=1;return sum;
}

又写完了咯

#include<cstdio>
#include<iostream>
#include<cmath>
using namespace std;
int M;
int tr[2000001],n,m;
inline void build()
{for(M=1;M<=n+1;M<<=1);for(int i=M+1;i<=M+n;i++)scanf("%d",&tr[i]);int t=M+n;for(t=M;t;t--){int mi=min(tr[t>>1],tr[t>>1|1]);tr[t>>1]-=mi;tr[t>>1|1]-=mi;tr[t]+=mi;}
}
inline void add(int s,int t,int v)
{for(s=s+M-1,t=t+M+1;s^t^1;s>>=1,t>>=1){if(~s&1)    tr[s^1]+=v;if(t&1)        tr[t^1]+=v;int mi=min(tr[s^1],tr[s]);tr[s]-=mi;tr[s^1]-=mi;tr[s>>1]+=mi;mi=min(tr[t^1],tr[t]);tr[t]-=mi;tr[t^1]-=mi;tr[t>>1]+=mi;}while(s){int mi=min(tr[s],tr[s^1]);tr[s]-=mi;tr[s^1]-=mi;tr[s>>1]+=mi;s>>=1;}
}
inline int query(int x)
{int sum=0,p=M+x;while(p)sum+=tr[p],p>>=1;return sum;
}
int main()
{cin>>n>>m;build();for(int i=1;i<=m;i++){int x;scanf("%d",&x);if(x==1){int s,t,v;scanf("%d%d%d",&s,&t,&v); add(s,t,v);}else{int s;scanf("%d",&s);printf("%d\n",query(s));}}
}

感谢读到这里

这篇关于非递归式(zkw)线段树详解(一)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/155754

相关文章

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

poj3468(线段树成段更新模板题)

题意:包括两个操作:1、将[a.b]上的数字加上v;2、查询区间[a,b]上的和 下面的介绍是下解题思路: 首先介绍  lazy-tag思想:用一个变量记录每一个线段树节点的变化值,当这部分线段的一致性被破坏我们就将这个变化值传递给子区间,大大增加了线段树的效率。 比如现在需要对[a,b]区间值进行加c操作,那么就从根节点[1,n]开始调用update函数进行操作,如果刚好执行到一个子节点,

hdu1394(线段树点更新的应用)

题意:求一个序列经过一定的操作得到的序列的最小逆序数 这题会用到逆序数的一个性质,在0到n-1这些数字组成的乱序排列,将第一个数字A移到最后一位,得到的逆序数为res-a+(n-a-1) 知道上面的知识点后,可以用暴力来解 代码如下: #include<iostream>#include<algorithm>#include<cstring>#include<stack>#in

hdu1689(线段树成段更新)

两种操作:1、set区间[a,b]上数字为v;2、查询[ 1 , n ]上的sum 代码如下: #include<iostream>#include<algorithm>#include<cstring>#include<stack>#include<queue>#include<set>#include<map>#include<stdio.h>#include<stdl

OpenHarmony鸿蒙开发( Beta5.0)无感配网详解

1、简介 无感配网是指在设备联网过程中无需输入热点相关账号信息,即可快速实现设备配网,是一种兼顾高效性、可靠性和安全性的配网方式。 2、配网原理 2.1 通信原理 手机和智能设备之间的信息传递,利用特有的NAN协议实现。利用手机和智能设备之间的WiFi 感知订阅、发布能力,实现了数字管家应用和设备之间的发现。在完成设备间的认证和响应后,即可发送相关配网数据。同时还支持与常规Sof

hdu 1754 I Hate It(线段树,单点更新,区间最值)

题意是求一个线段中的最大数。 线段树的模板题,试用了一下交大的模板。效率有点略低。 代码: #include <stdio.h>#include <string.h>#define TREE_SIZE (1 << (20))//const int TREE_SIZE = 200000 + 10;int max(int a, int b){return a > b ? a :

hdu 1166 敌兵布阵(树状数组 or 线段树)

题意是求一个线段的和,在线段上可以进行加减的修改。 树状数组的模板题。 代码: #include <stdio.h>#include <string.h>const int maxn = 50000 + 1;int c[maxn];int n;int lowbit(int x){return x & -x;}void add(int x, int num){while

6.1.数据结构-c/c++堆详解下篇(堆排序,TopK问题)

上篇:6.1.数据结构-c/c++模拟实现堆上篇(向下,上调整算法,建堆,增删数据)-CSDN博客 本章重点 1.使用堆来完成堆排序 2.使用堆解决TopK问题 目录 一.堆排序 1.1 思路 1.2 代码 1.3 简单测试 二.TopK问题 2.1 思路(求最小): 2.2 C语言代码(手写堆) 2.3 C++代码(使用优先级队列 priority_queue)

poj 1127 线段相交的判定

题意: 有n根木棍,每根的端点坐标分别是 px, py, qx, qy。 判断每对木棍是否相连,当他们之间有公共点时,就认为他们相连。 并且通过相连的木棍相连的木棍也是相连的。 解析: 线段相交的判定。 首先,模板中的线段相交是不判端点的,所以要加一个端点在直线上的判定; 然后,端点在直线上的判定这个函数是不判定两个端点是同一个端点的情况的,所以要加是否端点相等的判断。 最后

K8S(Kubernetes)开源的容器编排平台安装步骤详解

K8S(Kubernetes)是一个开源的容器编排平台,用于自动化部署、扩展和管理容器化应用程序。以下是K8S容器编排平台的安装步骤、使用方式及特点的概述: 安装步骤: 安装Docker:K8S需要基于Docker来运行容器化应用程序。首先要在所有节点上安装Docker引擎。 安装Kubernetes Master:在集群中选择一台主机作为Master节点,安装K8S的控制平面组件,如AP