线段树入门&lazy思想

线段树入门&lazy思想线段树

线段树将区间分成若干个子区间,子区间又继续分,直到区间为一个点(区间左值等于右值)

对于父区间[a,b],其子区间为[a,(a+b)/2][(a+b)/2+1,b] 

用于求区间的值,如区间最值、区间的和等。

代码实现中,约定结点下标从1开始,所以某结点下标为x,那么左儿子下标为2x,右儿子下标为2x+1,父结点下标为x/2。

线段树入门&lazy思想


常用符号
符号 等价 意义
rt<<1 rt*2 左子树的编号


rt<<1|1
rt*2+1右子树的编号


 (l+r)>>1


(l+r)/2区间长度的一半


常用宏定义

#define Mid ((l+r)>>1) //注意括号 #define lson rt<<1,l,Mid //左结点 #define rson rt<<1|1,Mid+1,r //右结点


建树

建树丛根结点开始,递归建立左右子树,直到叶子结点,然后反向赋值,父结点的值 = F(左结点的值,右结点的值),这个F是依据题意变的,如果是区间最大则为max()

void build(int rt,int l,int r) //建编号为rt的区间为[l,r]的树,主函数传进来的固定是(1,1,n) { if(l==r){ //叶子结点赋初值,注意下标,Max的是编号,val原数组的是l,看图可以理解 Max[rt] = val[l]; }else{ //建左右子树 build(lson); build(rson); Max[rt] = max( Max[rt<<1], Max[rt<<1|1]); //父结点Max值为Max(左子结点,右子结点) } }

查询


查询为区间查询(只是查询某个点的话不需要线段树),即在区间里查询某个特性值,每次查询都是从跟结点开始往下,根据查询区间和当前区间的区间位置判断是要去左右子区间查询,还是直接返回。如果被查询区间是查询区间的子区间则直接返回子区间的值,如在[1,6]里查询[1,12]就返回[1,6]的值,不再往下查询。

void query(int rt,int l,int r,int L,int R) //在[l,r]里查询[L,R]的值,[L,R]一直不变,[l,r]变 { if(L <= l && r <= R){ ans1 = max(ans1,Max[rt]); ans2 = min(ans2,Min[rt]); }else{ if( L <= Mid) //查询区间在当前区间的左半区间有内容,如在[1,6]里查询[2,3] query(lson,L,R); if( R > Mid) //同理去右子区间,注意不能有else,因为有横跨左右的情况,如[1,6]里查询[2,5] query(rson,L,R); } }

更新

更新分为单点更新和区间更新,区间更新等会在下面讲述,而单点更新跟普通区间查询差不多

void update(int rt,int l,int r,int pos,int num) { if(l == r && r == pos){ //到对应的叶结点 Max[rt] = num; }else{ if( pos <= Mid) update(lson,pos,num); if( pos > Mid) //或者直接else,点不可能同时在两个区间里 update(rson,pos,num); Max[rt] = max( Max[rt<<1], Max[rt<<1|1]); } }

数组大小要为原数据范围的4倍,证明点这里

题目

POJ 3264 Balanced Lineup 求区间的最大值-最小值

#include 
    
      #include 
     
       #include 
      
        using namespace std; #define FOR( i , a , b ) for ( int i = a ; i <= b ; ++ i ) #define RE freopen("1.in","r",stdin); #define bug(x) cout<<#x<<":"<<(x)< 
       
         >1) //括号! #define lson rt<<1,l,Mid #define rson rt<<1|1,Mid+1,r const int maxn= 50005; const int inf=0x3f3f3f3f; int Max[maxn<<2],Min[maxn<<2]; int ans1,ans2; void build(int rt,int l,int r) { if(l==r){ scanf("%d",&Max[rt]); Min[rt] = Max[rt]; }else{ build(lson); build(rson); Max[rt] = max( Max[rt<<1], Max[rt<<1|1]); Min[rt] = min( Min[rt<<1], Min[rt<<1|1]); } } void query(int rt,int l,int r,int L,int R) { if(L <= l && r <= R){ ans1 = max(ans1,Max[rt]); ans2 = min(ans2,Min[rt]); }else{ if( L <= Mid) query(lson,L,R); if( R > Mid) query(rson,L,R); } } int main() { int n,m,L,R; while(scanf("%d%d",&n,&m)!=EOF){ build(1,1,n); while(m--){ ans1 = -inf,ans2 = inf; scanf("%d%d",&L,&R); query(1,1,n,L,R); printf("%d\n", ans1-ans2); } } return 0; } 
        
       
      
    


HDU 1754  I Hate It单点更新,区间查询最大


#include 
    
      #include 
     
       #include 
      
        using namespace std; #define FOR( i , a , b ) for ( int i = a ; i <= b ; ++ i ) #define RE freopen("1.in","r",stdin); #define bug(x) cout<<#x<<":"<<(x)< 
       
         >1) #define lson rt<<1,l,Mid #define rson rt<<1|1,Mid+1,r const int maxn= +5; const int inf=0x3f3f3f3f; int Max[maxn<<2]; int ans1,ans2; void build(int rt,int l,int r) { if(l==r){ scanf("%d",&Max[rt]); }else{ build(lson); build(rson); Max[rt] = max( Max[rt<<1], Max[rt<<1|1]); } } void update(int rt,int l,int r,int pos,int num) { if(l == r && r == pos){ Max[rt] = num; }else{ if( pos <= Mid) update(lson,pos,num); if( pos > Mid) update(rson,pos,num); Max[rt] = max( Max[rt<<1], Max[rt<<1|1]); } } int query(int rt,int l,int r,int L,int R) { if(L <= l && r <= R){ return Max[rt]; }else{ int tmp = -1; if( L <= Mid) tmp = max(tmp,query(lson,L,R)); if( R > Mid) tmp = max(tmp,query(rson,L,R)); return tmp; } } int main() { // RE int n,m,L,R; char op; while(scanf("%d%d",&n,&m)!=EOF){ build(1,1,n); getchar(); while(m--){ scanf("%c%d%d%*c",&op,&L,&R); if(op=='Q') printf("%d\n",query(1,1,n,L,R)); else update(1,1,n,L,R); } } return 0; } 
        
       
      
    


HDU 1166 敌兵布阵

单点更新,查询区间和【此题树状数组解法转见→】

#include 
    
      #include 
     
       #include 
      
        using namespace std; #define FOR( i , a , b ) for ( int i = a ; i <= b ; ++ i ) #define RE freopen("1.in","r",stdin); #define bug(x) cout<<#x<<":"<<(x)< 
       
         >1) #define lson rt<<1,l,Mid #define rson rt<<1|1,Mid+1,r const int maxn= 50000+5; const int inf=0x3f3f3f3f; int sum[maxn<<2]; int ans1,ans2; void build(int rt,int l,int r) { if(l==r){ scanf("%d",&sum[rt]); }else{ build(lson); build(rson); sum[rt] = sum[rt<<1] + sum[rt<<1|1]; } } void update(int rt,int l,int r,int pos,int num) { if(l == r && r == pos){ sum[rt] += num; }else{ if( pos <= Mid) update(lson,pos,num); else update(rson,pos,num); sum[rt] = sum[rt<<1] + sum[rt<<1|1]; } } int query(int rt,int l,int r,int L,int R) { if(L <= l && r <= R){ return sum[rt]; }else{ int tmp = 0; if( L <= Mid) tmp += query(lson,L,R); if( R > Mid) tmp += query(rson,L,R); return tmp; } } int main() { // RE int n,m,L,R,t; char op[10]; scanf("%d",&t); for(int cas=1;cas<=t;cas++){ scanf("%d",&n); build(1,1,n); printf("Case %d:\n",cas); while(scanf("%s",op),op[0]!='E'){ scanf("%d%d",&L,&R); if(op[0]=='Q') printf("%d\n", query(1,1,n,L,R)); else if(op[0]=='A') update(1,1,n,L,R); else update(1,1,n,L,-R); } } return 0; } 
        
       
      
    

Lazy

区间成段更新

Lazy:正常来说,区间改值,当更改某个区间的值的时候,子区间也该跟着更改,这样容易TLE。

Lazy思想就是更新到某个区间的时候,就先给这个区间打上标记,标记内容是需要更新的值,并把子区间的值改为子区间对应的值,清除该区间的lazy标记;然后return,不去更新子区间。当下一次更新或查询等需要访问该区间的子区间的时候再把该区间的lazy和其他信息送回子区间。


举个简单粗暴的例子:

对应下面的那个图,假如目的是求和,现在要给[1,6] 的值都加2,那么我们从[1,12]->[1,6],然后[1,6]的sum值加上区间长度[ (6-1+1)*2 ],再把[1,6]的add[i]设置为2,就不再往下更新了【这里极大提高效率】。下一次更新/查询[1,6]的子区间时,我们将[1,6]原存的add值下传给[1,6]的两个直接子区间,再往下更新。假设在这种情况下,我们再更新[1,6]加3,则[1,6]的add值为2+3=5,然后我们查询[1,3],则从上往下经过[1,6]时把[1,6]的add值给了子区间[1,3]和[4,6],同时把sum[子区间]跟着子区间长度和add[父结点]改动,清除add[父节点]。【如果是查询间接子区间,则连续传递add值,也就是连续pushDown】


详细例子:假设update()是区间改值,query()是求和,所有叶子区间的和都为1,则[7,8]和[7,9]在build()的时候就附上了值(图中绿色字体)。假设此时我们更新[7,9]的值,改为2,则线段树从[1,12]->[7,12]->[7,9],然后把[7,9]打上值为2的标记,求和(求和直接用区间长度*此时更新的值)然后不去更新[7,8]和[9,9]了,他们值仍然是2和1,lazy值为0。

线段树入门&lazy思想


然后我们查询[7,8],当遍历经过[7,9]时

 if(add[i]) pushDown(i);

成立,把[7,9]的lazy标记2传给子区间[7,8]和[9,9],分别求这2个子区间的和,把[7,9]的lazy标记去掉,然后继续遍历,到[7,8]的时候直接返回答案。

线段树入门&lazy思想


HDU 1698 Just a Hook 区间改值,求和


#include 
       
         #include 
        
          #include 
         
           #include 
          
            using namespace std; #define ll long long #define FOR( i , a , b ) for ( int i = a ; i <= b ; ++ i ) #define RE freopen("1.in","r",stdin); #define WE freopen("1.out","w",stdout); #define MOD 10009 #define bug(x) cout<<#x<<":"<<(x)< 
           
             >1) #define lson rt<<1,l,Mid #define rson rt<<1|1,Mid+1,r const int maxn = ; int sum[maxn<<2],add[maxn<<2]; void build(int rt,int l,int r) { add[rt] = 0; if(l == r){ sum[rt] = 1; }else{ build(lson); build(rson); sum[rt]=sum[rt<<1]+sum[rt<<1|1]; } } void pushDown(int rt,int len) { add[rt<<1] = add[rt<<1|1] = add[rt]; sum[rt<<1] = (len-(len>>1))*add[rt]; sum[rt<<1|1] = (len>>1)*add[rt]; add[rt] = 0; } void update(int rt,int l,int r,int L,int R,int z) { if(L <= l && r <= R){ add[rt] = z; sum[rt] = (r-l+1)*z; }else{ if(add[rt]) pushDown(rt,r-l+1); if(L <= Mid) update(lson,L,R,z); if(R > Mid) update(rson,L,R,z); sum[rt] = sum[rt<<1] + sum[rt<<1|1]; } } int main() { // RE; int t,n,q,x,y,z; int cnt=1; scanf("%d",&t); while(t--){ scanf("%d%d",&n,&q); build(1,1,n); while(q--){ scanf("%d%d%d",&x,&y,&z); update(1,1,n,x,y,z); } printf("Case %d: The total value of the hook is %d.\n", cnt++,sum[1]); } return 0; } 
            
           
          
         
       

相关链接:线段树题目合集                  二维线段树                       线段树模板












版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/178189.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月26日 下午5:49
下一篇 2026年3月26日 下午5:50


相关推荐

  • python面试题目及答案(数据库常见面试题及答案)

    Python是目前编程领域最受欢迎的语言。在本文中,我将总结Python面试中最常见的50个问题。每道题都提供参考答案,希望能够帮助你在2019年求职面试中脱颖而出,找到一份高薪工作。这些面试题涉及Python基础知识、Python编程、数据分析以及Python函数库等多个方面。Q1、Python中的列表和元组有什么区别?Q2、Python的主要功能是什么?Python是一种解释型…

    2022年4月17日
    71
  • 系统运维面试题

    系统运维面试题1.什么是运维?什么是游戏运维?1)运维是指大型组织已经建立好的网络软硬件的维护,就是要保证业务的上线与运作的正常,在他运转的过程中,对他进行维护,他集合了网络、系统、数据库、开发、安全、监控于一身的技术,运维又包括很多种,有DBA运维、网站运维、虚拟化运维、监控运维、游戏运维等等2)游戏运维又有分工,分为开发运维、应用运维(业务运维)和系统运维开发运维:是给应用运维开发运维工具和运维平台的应用运维:是给业务上线、维护和做故障排除的,用开发运维开发出来的工具给业务上线、维护、做故障

    2022年6月1日
    212
  • php openssl生成证书,php中使用OpenSSL生成证书及加密解密[通俗易懂]

    php openssl生成证书,php中使用OpenSSL生成证书及加密解密[通俗易懂]摘要:这篇文章主要介绍了PHP中使用OpenSSL生成证书及加密解密,需要的朋友可以参考下依赖于OpenSSL扩展/*加密解密*/functionauthcode($string,$operation=’E’){$ssl_public=file_get_contents(DAT这篇文章主要介绍了PHP中使用OpenSSL生成证书及加密解密,需要的朋友可以参考下依赖于OpenSSL扩展…

    2026年1月25日
    5
  • 【2025年首选】AI大模型API中转站

    【2025年首选】AI大模型API中转站

    2026年3月15日
    2
  • nc报销系统用的什么java_财务NC系统是什么?

    nc报销系统用的什么java_财务NC系统是什么?展开全部 1 NC 系统是一个全面的预算管理平台 支持企业从销售计划 生产 e4b893e5b19e 计划 采购计划 费用计划 投资计划 资金计划 损益计划 资产负债计划的全面预算控制 实现了对经营行为的事前编制 事中控制和事后分析 2 NC 财务系统也叫 NC 系统 充分考虑了用户的个性化需求 提供了多种自我配置和客户

    2026年3月18日
    2
  • linux 使用rpm卸载软件的使用方法

    linux 使用rpm卸载软件的使用方法linux 上使用 rpm 安装的一些软件 该如何进行卸载呢 卸载步骤 1 先使用 rpm qa grep 软件包名称例如卸载 mysql rpm qa grepmysql2 使用 rpm enodeps 文件包名称 rpm enodepsmysql 5 0 77 4 el5 6 6rpm enodepslibdb dbd mysql 0 8 1a 1 2 2rpm enodepsmysql 5 0 77 4 el5 6 6rpm

    2026年3月18日
    2

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号