线段树(三):区间修改(一个标记且有顺序)

    快速操作序列$Ⅱ$:给出一个有$n$个元素的数组$A_1,A_2,..,,A_n$,设计一个数据结构支持以下两种操作:

  • $Set(L,R,v)$:把$A_L,A_{L+1}, ..., A_R$的值全部修改成$v$($v \geq 0$)
  • $Query(L,R)$:计算子序列$A_L,A_{L+1},...,A_R$的最小值

不难想到把$set$操作进行分解,记录在结点中,但很快就会发现一个问题,add操作的时间顺序不会影响结果,但set会。比如先执行$add(1,4,1)$再执行$add(2,3,2)$等价于先执行$add(2,3,2)$再执行$add(1,4,1)$,但先执行$set(1,4,1)$再执行$set(2,3,2)$却不等价于先执行$set()2,3,2($再执行$set(1,4,1)$。怎么办呢?

    简而言之,就是维护标记,包括标记下推和标记更新。当执行$update$操作时经过带标记结点时,需要将标记传推到左右子结点,同时消除自身的标记。$set$操作的标记更新很简单,直接用新的$setv$覆盖原来的$setv$。

    值得注意的是,与之前的$add$操作相比,代码中多了两处$maintain$的调用。这是因为只要标记下传,该子树的附加信息就必须重新计算,但是我们只进入其中一个子树,该子树在递归访问结束后自然会调用$maintain$,因此还需要针对不进行递归访问的子树调用$maintain$。

 1 void pushdown(int o)
 2 {
 3     if(setv[o] >= 0)   //有标记才传递,假设set的值非负
 4     {
 5         setv[2*o] = setv[2*o+1] = setv[o];
 6         setv[o] = -1;    //清除本结点标记
 7     }
 8 }
 9 
10 void maintain(int o, int L, int R)
11 {
12     if(setv[o] >= 0)  minv[o] = setv[o];
13     else
14     {
15        if(L == R)  minv[o] = a[L];
16        else  minv[o] = min(minv[2*o], minv[2*o+1]);
17    }
18 }
19 
20 int cl,cr,v;
21 void update(int o, int L, int R)
22 {
23     int M=  L + (R - L) / 2;
24     if(cl <= L && R <=cr)  setv[o] = v;
25     else
26     {
27         pushdown(o);   //进入左右子树前先下推标记
28         if(cl <= M)  update(2*o, L, M); else  maintain(2*o, L, M);
29         if(cr > M) update(2*o+1, M + 1, R); else  maintain(2*o+1, M+1, R);
30     }
31   maintain(o, L, R);
32 }

    查询操作跟之前类似,不同的是,当两个add操作存在祖先-后代关系时是进行累加,而当两个set操作存在祖先-后代关系时,以祖先结点为准即可(为什么?祖先结点的set肯定是后出现的)。

 1 int ql, qr;
 2 int query(int o, int L, int R)
 3 {
 4     int M = L + (R-L)/2;
 5     if(setv[o] >= 0)  return setv[o];
 6     if(ql <=  L && R <= qr)  return minv[o];
 7     else
 8     {
 9         int ans = INF;
10         if(ql <= M)  ans = min(ans, query(2*o, L, M));
11         if(qr > M)  ans = min(ans, query(2*o+1, M+1, R));
12         return ans;
13     }
14 }

 

posted @ 2019-06-29 01:11  Rogn  阅读(428)  评论(0编辑  收藏  举报