当前位置: 首页 > news >正文

算法总结:数据结构——树状数组线段树

直接上题吧:
已知一个数列{ a i } \{a_i\}{ai},你需要进行下面两种操作:
1.将某区间每一个数/某一个数加上k kk
2.求出某区间每一个数的和/某一个数的值。

首先,暴力前缀和肯定是不行的,因为它无法快速完成区间的修改(不然还用啥树状数组/线段树),
那么,直接开始:

树状数组

思想

如图,a aa数组是输入的数列,c cc数组存的是管辖范围内(管辖范围如图)的前缀和。

不难发现,c [ x ] c[x]c[x]的管辖范围是从x往前的l o w b i t ( x ) lowbit(x)lowbit(x)个数。
(l o w b i t lowbitlowbit表示一个数二进制的最低一位1,如l o w b i t ( 5 ) = 1 , l o w b i t ( 12 ) = 4 lowbit(5)=1,lowbit(12)=4lowbit(5)=1lowbit(12)=4)

另:如何求lowbit

直接用x&(-x)即可。
就这么粗暴的O ( 1 ) O(1)O(1)(^▽ ^ )原理嘛,大概就是神奇的补码嗯对

修改(点修)

在树状数组内,如果要修改某个数的值,只需要把所有管辖它的c cc修改掉即可。
For example,如果要修改a [ 5 ] a[5]a[5]的值,那么只需修改c [ 5 ] , c [ 6 ] , c [ 8 ] , c [ 16 ] 的 c[5],c[6],c[8],c[16]的c[5]c[6]c[8]c[16]值即可。
那么如何找到下一个管辖区间呢?
还是l o w b i t lowbitlowbit,只需要用x xx加上l o w b i t ( x ) lowbit(x)lowbit(x),就能找到下一个区间嗯,就是这么神奇
如此,修改的时间复杂度便降到了O ( l o g n ) O(log\ n)O(logn)以下。

查询(区查)

同样,查询时,也只要顺着找上一个管辖区间一直加, 就能得到[ 1 , x ] [1,x][1,x]区间的前缀和。
要找到上一个区间,也只需要用x xx减去l o w b i t ( x ) lowbit(x)lowbit(x)
最后,求[ x , y ] [x,y][x,y]的区间和,再用[ 1 , y ] [1,y][1,y]的区间和减去[ 1 , x − 1 ] [1,x-1][1,x1]的区间和即可。
时间复杂度同样小于O ( l o g n ) O(log\ n)O(logn)

代码(P3374点修区查)

#include<bits/stdc++.h>usingnamespacestd;#defineN500001intc[N],n,m;intlowbit(intx){returnx&(-x);}voidadd(intx,intk){while(x<=n){c[x]+=k;x+=lowbit(x);}}intsearch(intx){ints=0;while(x){s+=c[x];x-=lowbit(x);}returns;}intmain(){cin>>n>>m;for(inti=1;i<=n;i++){inta;cin>>a;add(i,a);}while(m--){intop,x,y;cin>>op>>x>>y;if(op==1)add(x,y);elsecout<<search(y)-search(x-1)<<endl;}return0;}

(叠甲:函数名称不严谨,仅供图一乐)

进阶版(P3368区修点查)

区修,就肯定不能像刚才一样挨个改了,这时候就需要点儿更快的方法——差分。
本人自己是肯定想不出来的,因为根本就没学过差分现学的哈哈哈o-o
用差分的思想,可以知道,修改[ x , y ] [x,y][x,y]区间的值,可以在x xx处加k kk,在y + 1 y+1y+1处减k kk
询问某个数的值,也只需要将原数加上差分数组前缀和。
然后,把以上东西套个树状数组,遂成。

代码
#include<bits/stdc++.h>usingnamespacestd;#defineN500001#definelllonglongll n,m,a[N],c[N];lllowbit(ll x){returnx&(-x);}voidadd(ll x,ll k){while(x<=n){c[x]+=k;x+=lowbit(x);}}llsearch(ll x){ll s=0;while(x){s+=c[x];x-=lowbit(x);}returns+a[x];}intmain(){cin>>n>>m;for(inti=1;i<=n;i++)cin>>a[i];ll op,x,y,k;for(inti=1;i<=n;i++){add(i,a[i]-a[i-1]);}//差分树状数组初始化while(m--){cin>>op;if(op==1){cin>>x>>y>>k;add(x,k);add(y+1,-k);}else{cin>>x;cout<<search(x)<<endl;}}return0;}

线段树

想完成区修区查,线段树是更优解。

思想

建一棵二叉搜索树(英文名曰B i n a r y S e a r c h T r e e , B S T Binary\ Search\ Tree,BSTBinarySearchTree,BST),每个结点管辖一个区间(用t r e e [ o ] tree[o]tree[o]来表示结点o oo管辖区间数的总和)。
根据BST的性质:结点o oo的左儿子编号为2 o 2o2o,右儿子的编号为2 o + 1 2o+12o+1
如果o oo管辖的区间为[ l , r ] [l,r][l,r],那么左儿子管辖的区间为[ l , m i d ] ( m i d = l + r 2 ) [l,mid](mid=\frac{l+r}{2})[l,mid](mid=2l+r),右儿子管辖的区间为[ m i d + 1 , r ] [mid+1,r][mid+1,r]

查询

查询还是蛮简单的(相对于修改)。
对于查询区间[ x , y ] [x,y][x,y],如果结点o oo所管辖的区间[ l , r ] [l,r][l,r]非严格包含于区间[ x , y ] [x,y][x,y](即x ⩽ l x\leqslant lxlr ⩽ y r\leqslant yry),那么在答案中直接加上t r e e [ o ] tree[o]tree[o]即可。
如果两区间交叉,那么要往下找,并重复以上操作,直到完全求出答案。
时间复杂度O ( l o g n ) O(log\ n)O(logn)

修改

有亿点复杂……

key:懒标记

顾名思义,如果在修改时把数据一个一个往下传会特别慢,所以就要用更“懒”的办法修改数据。
我们不妨把要修改的k kk先暂存在l a z y lazylazy数组中(多好听的名字),等到需要时再下传。

要在[ x , y ] [x,y][x,y]区间内给每个数加上k kk,对于结点o oo,如果其所管辖的区间[ l , r ] [l,r][l,r]非严格包含于区间[ x , y ] [x,y][x,y],那么直接修改t r e e [ o ] tree[o]tree[o](加上k × ( r − l + 1 ) k×(r-l+1)k×(rl+1),相当于直接修改每个数),同时给o oo做上懒标记,也就是给l a z y [ o ] lazy[o]lazy[o]加上k kk,查询时再按需要下传。
如果两区间交叉,那么就要将懒标记下传给自己的左右儿子,如此循环,直到整个区间都被修改完毕。
时间复杂度O ( l o g n ) O(log\ n)O(logn)

代码(P3372区修区查)

线段树千好万好,但是代码量有亿点多(光是上边这么多就把我写力竭了),代码细节有亿点繁琐……
从开空间开始,线段树就在做局:用线段树必须开4倍空间!证明我也不会,开就完了
然后后面的建树、传懒标记、修改、查询四大函数,一写一个不吱声……
算了,直接欣赏一下吧(写完真力竭了):

#include<bits/stdc++.h>usingnamespacestd;#defineN400001//极品4倍空间#definelllonglongll n,m,tree[N],lazy[N],a[N];voidbuild(ll o,ll l,ll r){if(l==r){tree[o]=a[l];return;}ll mid=(l+r)/2;build(o*2,l,mid);build(o*2+1,mid+1,r);tree[o]=tree[o*2]+tree[o*2+1];}//建树,防止被CCF老年机卡常voidpushdown(ll o,ll l,ll r){ll mid=(l+r)/2;if(lazy[o]){tree[o*2]+=(mid-l+1)*lazy[o];tree[o*2+1]+=(r-mid)*lazy[o];lazy[o*2]+=lazy[o];lazy[o*2+1]+=lazy[o];lazy[o]=0;}}//下传懒标记voidupdate(ll o,ll l,ll r,ll x,ll y,ll k){//结点编号,管辖左端点,管辖右端点,修改左端点,修改右端点,要修改的值if(x<=l&&r<=y){tree[o]+=(r-l+1)*k;lazy[o]+=k;return;}ll mid=(l+r)/2;pushdown(o,l,r);if(x<=mid)update(o*2,l,mid,x,y,k);if(y>mid)update(o*2+1,mid+1,r,x,y,k);tree[o]=tree[o*2]+tree[o*2+1];}//修改llquery(ll o,ll l,ll r,ll x,ll y){//结点编号,管辖左端点,管辖右端点,查询左端点,查询右端点if(x<=l&&r<=y)returntree[o];ll mid=(l+r)/2,sum=0;pushdown(o,l,r);if(x<=mid)sum+=query(o*2,l,mid,x,y);if(y>mid)sum+=query(o*2+1,mid+1,r,x,y);returnsum;}//查询intmain(){cin>>n>>m;for(inti=1;i<=n;i++)cin>>a[i];build(1,1,n);ll op,x,y,k;while(m--){cin>>op;if(op==1){cin>>x>>y>>k;update(1,1,n,x,y,k);}else{cin>>x>>y;cout<<query(1,1,n,x,y)<<endl;}}return0;}

本人写代码已经很不爱换行了,基本上能一行塞下的东西就不会换行,留空行的行为更是不存在,线段树的代码也就只写了《63行》
要是再空几行,代码量《也就70行》
要是实际做题,代码量《也就100来行》
要是写个不太正常的代码……《也就200行》

By the way,谁懂一个被橙题卡住的蒟蒻看到连模版都冒绿光的满屏绿题的救赎感o(╥﹏╥)o

http://www.cnnetsun.cn/news/1611411.html

相关文章:

  • Tao-8k自动化运维脚本生成:应对服务器常见管理任务
  • Matlab与GME多模态向量模型联动:学术研究中的图像特征分析
  • Go语言中的Interface:面向接口编程
  • 3步解锁加密音乐:用Unlock Music重新掌控你的数字音乐资产
  • 实战演练:基于快马AI快速生成具备商品管理功能的.NET电商系统后端
  • League-Toolkit:基于LCU API的英雄联盟效率工具实战指南
  • 终极指南:Ghost Admin-X设计系统UI组件库的完整使用与扩展方法
  • 终极音乐解锁指南:如何用QMCDecode一键解密QQ音乐加密格式
  • 汇川小型机 H5U编写程序 设备采用回转hu小型机编写程序不含的硬件配置有ECT的总线
  • 告别HEIC预览盲区:让Windows用户轻松驾驭苹果图像格式
  • 从霍伟《机器人动力学与控制》到实操:Gluon_6L3机械臂最小惯性参数集推导全记录
  • 新手福音:基于快马平台零基础入门Ubuntu与OpenClaw机器人开发
  • intv_ai_mk11惊艳效果:自动检测用户提问歧义并提供2-3种可能意图供选择
  • ALNS算法调参实战指南:如何让你的路径规划求解器性能提升50%
  • 面试官冷笑:Agent 核心组件就这?AI Agent 架构全解析(非常详细),大模型开发从入门到精通,收藏这一篇就够了!
  • Fast DDS实战:用Wireshark抓包拆解HelloWorld示例的完整通信流程(附pcap文件)
  • 基于博途1200PLC+HMI四级传送带控制系统仿真-升级版 程序: 1、任务:系统由四条传送带构成
  • Phi-3-mini-128k-instruct部署案例:高校实验室用该镜像开展AI教学与实验验证
  • RTL WiFi驱动移植踩坑记:从源码到.ko文件的完整心路历程
  • 避坑指南:ESP32-CAM连接SD卡时,除了接线还要注意这几点(基于ESP-IDF例程)
  • 第1篇:一文搞懂:电力电子到底是什么?
  • S32DS实战指南->GPIO配置与按键控制LED应用
  • ai赋能idea社区版:让快马生成复杂设计模式代码,提升本地开发智能体验
  • DeOldify图像上色服务效果展示:看AI如何精准还原照片色彩
  • 终极免费离线绘图工具:draw.io桌面版完全使用指南
  • 抖音批量下载神器:一键获取无水印视频、合集和直播的完整指南
  • 3步解锁FGA智能工具:彻底解放F/GO玩家双手的效率提升指南
  • 3个步骤实现极致跨平台远程控制:BilldDesk Pro突破性体验
  • GLM-4.1V-9B-Base效果展示:中文语义优先的描述风格 vs 英文模型直译差异
  • 人均薪资2W+:做网络安全可以有多赚钱?_网络安全公司靠什么挣钱