线段树区间合并

 

参考资料:

  [1]:ACM线段树的区间合并(图文) [提取码:o380]

  [2]:博客

 

首先让我们来看这个问题:

  • 给出一个序列,由 0和1 组成
  • 给出一个区间 [ l , r ]
  • 求这个区间中只包含1的子串的最大长度

我们可以使用一下解法:

  • 区间 DP : 时间复杂度O(n3)的预处理,O(n1)的询问
  • 胡乱预处理+询问O(n2)

但是如果增加新的条件呢?

  • 给出a,b,要求将第a位的值改为b
  • 刚才给出的解法都是依靠预处理来达到之后的询问时间的
  • 如果我们要做修改操作,就要重新预处理

联想线段树的作用,他刚好可以处理区间查询,更新问题

  • 如果我们能够使用线段树来解决这类区间查询问题(但并不是简单的带修改求和或RMQ)
  • 我们可以达到询问的O(logn和查询的O(logn)
  • 线段树的建造是O(nlogn),查询的总时间就是O(nlogn)
  • 即使不带修改也要比前面的方法优秀

解决这类问题,线段树由固定的套路

  • 这类问题基本是“带修改的区间查询”“连续”问题
  • 他可以问你一个区间的LCIS或者是一个区间的连续某数字等等

那么,介绍了线段树区间更新的功能后,开始回归正题,如何使用?

  如图所示,是由"1101111011"构造的一颗线段树,要求某区间连续的1的个数(一部分,都画出来太麻烦了,主要是用于方便理解概念用的)

  

  在此之前,先声明一下我的代码风格:

 1 #define ls(x) (x<<1)//左儿子
 2 #define rs(x) (x<<1|1)//右儿子
 3 
 4 struct SegmentTree
 5 {
 6     int l,r;
 7     int lsum,rsum;
 8     int sum;
 9     int mark;//懒惰标记
10     int mid()
11     {
12         return l+((r-l)>>1);
13     }
14     int len()
15     {
16         return r-l+1;
17     }
18 }segTree[4*maxn];

  ①首先介绍一下相关概念

    lsum : 从本节点区间最左端开始(向右)一共有lsum个连续的1;(segTree[1].lsum = 2)

    rsum : 从本节点区间最右端开始(向左)一共有rsum个连续的1;(segTree[1].rsum = 2)

    sum : 本区间一共最多有 sum 个连续的1;(segTree[1].sum = 4)

    mark : 区间更新的懒惰标记,比如,如果将区间[l,r]中所有的1变为0,那么 mark = 0 就意味着要将此区间中的所有1变为0;

  ②如何求解lsum,rsum,sum ? 下面介绍函数pushUp(int pos)的作用

    pushUp(int pos) : 把当前pos结点的"左右儿子的节点"信息更新到pos结点;

    例如,假设求出 1号节点"1101111011"的左右儿子节点的lsum,rsum,sum,那么便可通过pushUp(1)来求出1号节点的lsum,rsum,sum值;

    segTree[1].lsum = segTree[ls(1)].lsum;

    segTree[1].rsum = segTree[rs(1)].rsum;

    这两个操作是毋庸置疑的,1号节点的lsum至少为ls(1)号节点的lsum,但有没有可能比 segTree[ls(1)].lsum 大呢?

    答案是肯定的,如果 segTree[ls(1)].lsum = segTree[ls(1)].len(),那么

    segTree[1].lsum = segTree[ls(1)].lsum + segTree[rs(1)].rsum;

    如上图,如果将1号节点的左儿子中的'0'改为'1',那么segTree[1].lsum = 5+2 = 7;

    segTree[1].rsum 同理;

    那segTree[1].sum 该如何求呢?

    很显然,它等于 (左儿子的sum值,右儿子的sum值,左儿子的rsum+右儿子的lsum值)的最大值;

    pushUp()函数代码如下:

 1 //返回三者最大值
 2 int Max(int a,int b,int c)
 3 {
 4     return max(max(a,b),c);
 5 }
 6 void pushUp(int pos)
 7 {
 8     segTree[pos].lsum=segTree[ls(pos)].lsum;
 9     segTree[pos].rsum=segTree[rs(pos)].rsum;
10     
11     //判断是否可以增加
12     if(segTree[pos].lsum == segTree[ls(pos)].len())
13         segTree[pos].lsum += segTree[rs(pos)].lsum;
14     if(segTree[pos].rsum == segTree[rs(pos)].len())
15         segTree[pos].rsum += segTree[ls(pos)].rsum;
16         
17     segTree[pos].sum=Max(segTree[ls(pos)].sum,
18                          segTree[rs(pos)].sum,
19                          segTree[ls(pos)].rsum+segTree[rs(pos)].lsum);
20 }

  ③介绍完pushUp(int pos)函数后,下面来介绍pushDown(int pos)函数的作用

    pushDown(int pos) : 把当前pos结点的信息传递给儿子结点

    还记得区间更新懒惰标记中的pushDown(int pos)函数吗?

    之所以能够正确求解,就是这个函数的作用,在区间更新,查询操作中,每来到一个大区间,都要将这个区间的

    懒惰标记向下传递,这样才不至于出错。

    那么线段树区间合并中的pushDown(int pos)的作用也差不多;

    假设有两种操作

    1.将区间[l,r]全变为'0';

    2.将区间[l,r]全变为'1';

    那么,相应的,mark就需要有三个取值:

    mark = -1 : 不做任何操作;

    mark =  1 : 将区间[l,r]全变为'1';

    mark =  0 : 将区间[l,r]全变为'0';

    如果segTree[pos].mark = 1,那么在向下传递标记的时候,左右儿子的lsum,rsum,sum分别全都变为segTree[ls(pos)].len(),segTree[rs(pos)].len();

    如果segTree[pos].mark = 0,那么在向下传递标记的时候,左右儿子的lsum,rsum,sum全都变为0

    pushDown(int pos)代码如下:

 1 void F(int pos,int val)
 2 {
 3     segTree[pos].lsum=val;
 4     segTree[pos].rsum=val;
 5     segTree[pos].sum=val;
 6 }
 7 /**
 8     mark=-1 : no operator
 9     mark= 1 : 0变成1
10     mark= 0 : 1变成0
11 */
12 void pushDown(int pos)
13 {
14     int &mark=segTree[pos].mark;
15     if(mark == -1)
16         return ;
17     segTree[ls(pos)].mark=mark;
18     segTree[rs(pos)].mark=mark;
19     if(mark == 0)
20     {
21         F(ls(pos),0);
22         F(rs(pos),0);
23     }
24     else
25     {
26         F(ls(pos),segTree[ls(pos)].len());
27         F(rs(pos),segTree[rs(pos)].len());
28     }
29     mark=-1;
30 }

  ④介绍完主要的pushUp(),pushDown()后,建树,查询,更新操作就比较简单了,具体细节看代码

 

 

 

  

转载于:https://www.cnblogs.com/violet-acmer/articles/10519956.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值