[PKU 3468] 线段树(三) {Lazy-Tag思想}

{

继续讨论线段树

一个很重要的思想

Lazy-Tag

}

 

先看一个具体问题吧 PKU 3468

http://poj.org/problem?id=3468

题意很清楚 1 ≤ N,Q ≤ 100000.

"C a b c" means adding c to each of Aa, Aa+1, ... , Ab. -10000 ≤ c ≤ 10000.
"Q a b" means querying the sum of Aa, Aa+1, ... , Ab.

 

用朴素的做法是O(NQ)的 明显TLE

由于是区间统计问题 我们尝试用线段树解决

先考虑线段树节点记录什么

左右儿子 区间范围是必须的:ls[] rs[] l[] r[]

为了回答询问我们还要记录一个S[]来保存当前区间的和

*建树不是很难 注意要把s[]值从儿子处递推上来

 

 
  
1 procedure build(a,b:longint);
2   var x,mid:longint;
3   begin
4 inc(tt); x: = tt;
5 l[x]: = a; r[x]: = b;
6   if b - a = 1
7 then begin
8 s[x]: = m[b];
9 exit; end
10 else begin
11 mid: = (a + b)shr 1 ;
12 ls[x]: = tt + 1 ; build(a,mid);
13 rs[x]: = tt + 1 ; build(mid,b);
14 s[x]: = s[ls[x]] + s[rs[x]];
15 end ;
16   end ;

 

 

*当我们碰到C操作的时候把被a到b之间的区间覆盖所有线段树区间都修改一下

  +第三行判断是否被完全覆盖 是就直接修改s[]值

  +第五-七行判断是否有交集 递归修改

  +第八行 当且仅当没有被完全覆盖 才从儿子处得到新的s[]值

 

 
  
1 procedure insert(x,a,b,c:longint);
2   var mid:longint;
3   if (a <= l[x]) and (r[x] <= b)
4 then s[x]: = s[x] + (r[x] - l[x]) * c;
5 mid: = (l[x] + r[x])shr 1 ;
6   if a < mid then insert(ls[x],a,b,c);
7   if mid < b then insert(rs[x],a,b,c);
8   if not (a <= l[x]) and (r[x] <= b)
9 then s[x]: = s[ls[x]] + s[rs[x]];
10   end ;    

 

 

*碰到Q操作的时候就递归查询所有在a到b区间内的子区间 求和得到答案

  -第五行如果被所查区间完全覆盖 直接返回值

  -第10-14行 根据交集情况递归查询

 

 
  
1 function query(x,a,b:longint):int64;
2   var mid:longint;
3 ans:int64;
4   begin
5   if (a <= l[x]) and (r[x] <= b)
6 then begin
7 query: = s[x];
8 exit;
9 end ;
10 ans: = 0 ;
11 mid: = (l[x] + r[x])shr 1 ;
12   if a < mid then ans: = ans + query(ls[x],a,b);
13   if mid < b then ans: = ans + query(rs[x],a,b);
14 query: = ans;
15   end ;

 

 

 

可以证明 查询操作的复杂度不超过O(Log2N)[常数小于2] 可以满足问题需求

但是修改操作的最坏复杂度可以达到O(N) 而且常数比朴素还大 必须考虑优化

我们可以通过一下两个示意图 看到两个操作的差距

查询[2,10]

修改[2,10]

为了解决这个问题 著名的Lazy-Tag思想应运而生

每次都把所有区间修改了太慢了 不如懒一点 先把帐记着 到时候再做

懒-记帐=Lazy-Tag

怎么记录呢? 我们需要再加一个域v[] 这个域就是标记

用来记录当前节点为根的子树 是否需要统一加一个数 具体要加多少

我们的程序就要改一下了

  +每当碰到要当前区间完全被欲修改区间覆盖时 直接给加在标记上 然后退出

  +每次访问到一个节点时 首先清空当前节点的标记

    -访问包括各种操作 不管是插入 删除 还是查询 甚至是仅仅用到节点的s[]值

    -清空不仅仅是用标记更新当前节点 还包括把标记下传给左右子树

*我们用一个单独的过程clean来执行清空标记的操作

需要注意的是 叶子节点不用下传标记 否则RE

 

 
  
1 procedure clean(x:longint);
2   begin
3   if v[x] <> 0
4 then begin
5 s[x]: = s[x] + (r[x] - l[x]) * v[x];
6 if ls[x] <> 0 then v[ls[x]]: = v[ls[x]] + v[x];
7 if rs[x] <> 0 then v[rs[x]]: = v[rs[x]] + v[x];
8 v[x]: = 0 ;
9 end ;
10   end ;

*修改后的修改过程

 

  +第一行行先清空标记

  +第五行如果完全覆盖 修改标记 退出

  +第10-12行 递归修改子树

  +第13-14行 更新当前区间的s[]值 必须先清空标记!

 

 
  
1 procedure insert(x,a,b,c:longint);
2   var mid:longint;
3   begin
4 clean(x);
5   if (a <= l[x]) and (r[x] <= b)
6 then begin
7 v[x]: = v[x] + c;
8 exit;
9 end ;
10 mid: = (l[x] + r[x])shr 1 ;
11   if a < mid then insert(ls[x],a,b,c);
12 if mid < b then insert(rs[x],a,b,c);
13 clean(ls[x]); clean(rs[x]);
14 s[x]: = s[ls[x]] + s[rs[x]];
15 end ;

*修改后的查询

 

也要加上清空标记

而由于加上了这个递归完了更新s[]值也在所难免

 

 
  
1 function query(x,a,b:longint):int64;
2 var mid:longint;
3 ans:int64;
4 begin
5 clean(x);
6 if (a <= l[x]) and (r[x] <= b)
7 then begin
8 query: = s[x];
9 exit;
10 end ;
11 ans: = 0 ;
12 mid: = (l[x] + r[x])shr 1 ;
13 if a < mid then ans: = ans + query(ls[x],a,b);
14 if mid < b then ans: = ans + query(rs[x],a,b);
15 clean(ls[x]); clean(rs[x]);
16 s[x]: = s[ls[x]] + s[rs[x]];
17 query: = ans;
18 end ;

完整的代码在文章最后

 

当然Lazy-Tag可以用于极为强大的数据结构Splay

解决的这个问题 我们还没得到运用Lazy-Tag的一般规律

我还想到了另外一个问题 如果还带区间整体乘一个数呢?

 

接下来讨论双标记的问题

由于还要乘一个数 我们再加一个标记域

用u[]表示节点x为根的子树需要加u[x]

用v[]表示节点x为根的子树需要乘v[x]

实际运算中 我们发现乘法和加法的操作序列是不确定

所以我们要用2个标记来概括整个操作序列还需要考虑

回过头去看上一个问题 只有加法的时候 无论加法序列是什么样子的

根据加法的交换律和结合律 我们只要把整个序列的和保存下来就可以了

但是有乘有加就不能简单的累记了

比较好的方法是先规定标记的操作规则 先乘再加

对于任意节点x v[x]和u[x]表示当前的s[x]需要更新为s[x]*v[x]+u[x]

然后 每当我们要修改标记的时候

  如果要求给当前区间乘一个数c则将v[x]和u[x]都乘c

    (s[x]*v[x]+u[x])*c=s[x]*(v[x]*c)+(u[x]*c)

  如果要求给当前区间加一个数c 则只将u[x]+c即可

    (s[x]*v[x]+u[x])+c=s[x]*v[x]+(u[x]+c)

只有当对于任意一种操作

我们都保证能够无条件直接修改区间上的标记来达到效果

我们才可以方便地使用Lazy-Tag

否则当下传标记的时候还要考虑先清空儿子的标记

clean过程就变成递归的过程 效率又变回O(N)了

代码之需要在上一个问题上加以修改 贴在文章最后

核心的过程是clean

 

 
  
1 procedure clean(x:longint);
2 begin
3 if (u[x] <> 0 ) or (v[x] <> 1 )
4 then begin
5 s[x]: = s[x] * v[x] + u[x] * (r[x] - l[x]);
6 if ls[x] <> 0
7 then begin
8 v[ls[x]]: = v[ls[x]] * v[x];
9 u[ls[x]]: = u[ls[x]] * v[x] + u[x];
10 end ;
11 if rs[x] <> 0
12 then begin
13 v[rs[x]]: = v[rs[x]] * v[x];
14 u[rs[x]]: = u[rs[x]] * v[x] + u[x];
15 end ;
16 v[x]: = 1 ; u[x]: = 0 ;
17 end ;
18 end ;

我们再讨论一个问题 也是双标记的

 

维护一个序列

  支持操作给一个区间统一一个数

  和给一个区间统一修改成一个数

  还要询问区间和是多少

很容易想到额外维护三个域 s[]{Sum} c[]{Cover} d[]{Delta}

分别表示当前区间的和为s 当前区间被覆盖成c 当前区间需要加d

同样的 分析两个标记域d[]和c[]的关系

如果两个标记域都存在 那将会是一件棘手的事情 是先加再覆盖还是先覆盖再加呢?

我们可以发现以下几个性质

  如果对某个区间x覆盖了c[x]之后 以前的c[x]和d[x]都会自动消失

  对某个区间加d[x]之后 如果c[x]有值 可以直接加在c[x]上

这样我们就可以保证c[x]和d[x]最多只一个值存在 就没有上面的问题了

所以不妨规定c[]的优先级高于d[] 当如果c[x]有值就只执行c[x] 否则执行d[x]

我们又得到了一个结论 对于互相干涉的标记要定下明确的优先级 保证操作有序

核心还是clean过程

 

 
  
1 procedure down(x:longint);
2 begin
3 if c[x] <> key
4 then begin
5 s[x]: = c[x] * (r[x] - l[x]);
6 if ls[x] <> 0
7 then begin
8 c[ls[x]]: = c[x];
9 d[ls[x]]: = 0 ;
10 end ;
11 if rs[x] <> 0
12 then begin
13 c[rs[x]]: = c[x];
14 d[rs[x]]: = 0 ;
15 end ;
16 c[x]: = key;
17 end
18 else if d[x] <> 0
19 then begin
20 s[x]: = s[x] + d[x] * (r[x] - l[x]);
21 if ls[x] <> 0 then if c[ls[x]] <> key
22 then c[ls[x]]: = c[ls[x]] + d[x]
23 else d[ls[x]]: = d[ls[x]] + d[x];
24 if rs[x] <> 0 then if c[rs[x]] <> key
25 then c[rs[x]]: = c[rs[x]] + d[x]
26 else d[rs[x]]: = d[rs[x]] + d[x];
27 d[x]: = 0 ;
28 end ;
29 end ;

 

 

对Lazy-Tag的讨论就到这里

下一篇讨论 线段树的扩展

 

Bob HAN 原创 转载请注明出处 http://www.cnblogs.com/Booble/

 

附 完整代码

Simple

 

 
  
1 const maxn = 200000 ;
2   var l,r,ls,rs: array [ 1 ..maxn shl 1 ] of longint;
3 v,s: array [ 1 ..maxn shl 1 ] of int64;
4 m: array [ 1 ..maxn] of longint;
5 n,tt,i,q,a,b,c:longint;
6 ch,blank:char;
7   procedure build(a,b:longint);
8   var x,mid:longint;
9   begin
10 inc(tt); x: = tt;
11 l[x]: = a; r[x]: = b;
12 v[x]: = 0 ;
13   if b - a = 1
14 then begin
15 s[x]: = m[b];
16 exit; end
17 else begin
18 mid: = (a + b)shr 1 ;
19 ls[x]: = tt + 1 ; build(a,mid);
20 rs[x]: = tt + 1 ; build(mid,b);
21 s[x]: = s[ls[x]] + s[rs[x]];
22 end ;
23   end ;
24   procedure clean(x:longint);
25   begin
26   if v[x] <> 0
27 then begin
28 s[x]: = s[x] + (r[x] - l[x]) * v[x];
29 if ls[x] <> 0 then v[ls[x]]: = v[ls[x]] + v[x];
30 if rs[x] <> 0 then v[rs[x]]: = v[rs[x]] + v[x];
31 v[x]: = 0 ;
32 end ;
33   end ;
34   procedure insert(x,a,b,c:longint);
35   var mid:longint;
36   begin
37 clean(x);
38   if (a <= l[x]) and (r[x] <= b)
39 then begin
40 v[x]: = v[x] + c;
41 exit;
42 end ;
43 mid: = (l[x] + r[x])shr 1 ;
44   if a < mid then insert(ls[x],a,b,c);
45   if mid < b then insert(rs[x],a,b,c);
46 clean(ls[x]); clean(rs[x]);
47 s[x]: = s[ls[x]] + s[rs[x]];
48   end ;
49   function query(x,a,b:longint):int64;
50   var mid:longint;
51 ans:int64;
52   begin
53 clean(x);
54   if (a <= l[x]) and (r[x] <= b)
55 then begin
56 query: = s[x];
57 exit;
58 end ;
59 ans: = 0 ;
60 mid: = (l[x] + r[x])shr 1 ;
61   if a < mid then ans: = ans + query(ls[x],a,b);
62   if mid < b then ans: = ans + query(rs[x],a,b);
63 clean(ls[x]); clean(rs[x]);
64 s[x]: = s[ls[x]] + s[rs[x]];
65 query: = ans;
66   end ;
67   begin
68 assign(input, ' simple.in ' ); reset(input);
69 assign(output, ' simple.out ' ); rewrite(output);
70 readln(n,q);
71 for i: = 1 to n do
72 read(m[i]);
73 tt: = 0 ;
74 build( 0 ,n);
75 readln;
76 for i: = 1 to q do
77 begin
78 read(ch); read(blank);
79 case ch of
80 ' Q ' : begin
81 readln(a,b);
82 writeln(query( 1 ,a - 1 ,b));
83 end ;
84 ' C ' : begin
85 readln(a,b,c);
86 insert( 1 ,a - 1 ,b,c);
87 end ;
88 end ;
89 end ;
90 close(input); close(output);
91 end .
92

SuperSImple

 

 

 
  
1 const maxn = 100000 ;
2 var l,r,ls,rs: array [ 1 ..maxn shl 1 - 1 ] of longint;
3 u,v,s: array [ 1 ..maxn shl 1 - 1 ] of int64;
4 m: array [ 1 ..maxn] of longint;
5 n,q,tt,i,a,b,c:longint;
6 ch,blank:char;
7 procedure build(a,b:longint);
8 var mid,x:longint;
9 begin
10 inc(tt); x: = tt;
11 l[x]: = a; r[x]: = b;
12 u[x]: = 0 ; v[x]: = 1 ;
13 if b - a = 1
14 then s[x]: = m[b]
15 else begin
16 mid: = (a + b)shr 1 ;
17 ls[x]: = tt + 1 ; build(a,mid);
18 rs[x]: = tt + 1 ; build(mid,b);
19 s[x]: = s[ls[x]] + s[rs[x]];
20 end ;
21 end ;
22 procedure clean(x:longint);
23 begin
24 if (u[x] <> 0 ) or (v[x] <> 1 )
25 then begin
26 s[x]: = s[x] * v[x] + u[x] * (r[x] - l[x]);
27 if ls[x] <> 0
28 then begin
29 v[ls[x]]: = v[ls[x]] * v[x];
30 u[ls[x]]: = u[ls[x]] * v[x] + u[x];
31 end ;
32 if rs[x] <> 0
33 then begin
34 v[rs[x]]: = v[rs[x]] * v[x];
35 u[rs[x]]: = u[rs[x]] * v[x] + u[x];
36 end ;
37 v[x]: = 1 ; u[x]: = 0 ;
38 end ;
39 end ;
40 procedure mult(x,a,b,c:longint);
41 var mid:longint;
42 begin
43 clean(x);
44 if (a <= l[x]) and (r[x] <= b)
45 then begin
46 u[x]: = u[x] * c;
47 v[x]: = v[x] * c;
48 exit; end ;
49 mid: = (l[x] + r[x])shr 1 ;
50 if a < mid then mult(ls[x],a,b,c);
51 if mid < b then mult(rs[x],a,b,c);
52 clean(ls[x]); clean(rs[x]);
53 s[x]: = s[ls[x]] + s[rs[x]];
54 end ;
55 procedure plus(x,a,b,c:longint);
56 var mid:longint;
57 begin
58 clean(x);
59 if (a <= l[x]) and (r[x] <= b)
60 then begin
61 u[x]: = u[x] + c;
62 exit; end ;
63 mid: = (l[x] + r[x])shr 1 ;
64 if a < mid then plus(ls[x],a,b,c);
65 if mid < b then plus(rs[x],a,b,c);
66 clean(ls[x]); clean(rs[x]);
67 s[x]: = s[ls[x]] + s[rs[x]];
68 end ;
69 function query(x,a,b:longint):int64;
70 var mid:longint;
71 ans:int64;
72 begin
73 clean(x);
74 if (a <= l[x]) and (r[x] <= b)
75 then begin
76 query: = s[x];
77 exit; end ;
78 ans: = 0 ;
79 mid: = (l[x] + r[x])shr 1 ;
80 if a < mid then ans: = ans + query(ls[x],a,b);
81 if mid < b then ans: = ans + query(rs[x],a,b);
82 query: = ans;
83 clean(ls[x]); clean(rs[x]);
84 s[x]: = s[ls[x]] + s[rs[x]];
85 end ;
86 begin
87 assign(input, ' ssimple.in ' ); reset(input);
88 assign(output, ' ssimple.out ' ); rewrite(output);
89 readln(n,q);
90 for i: = 1 to n do
91 read(m[i]);
92 readln;
93 tt: = 0 ;
94 build( 0 ,n);
95 for i: = 1 to q do
96 begin
97 read(ch); read(blank);
98 case ch of
99 ' Q ' : begin
100 readln(a,b);
101 writeln(query( 1 ,a - 1 ,b));
102 end ;
103 ' M ' : begin
104 readln(a,b,c);
105 mult( 1 ,a - 1 ,b,c);
106 end ;
107 ' P ' : begin
108 readln(a,b,c);
109 plus( 1 ,a - 1 ,b,c);
110 end ;
111 end ;
112 end ;
113 close(input); close(output);
114 end .
115

Paint

 

 

 
  
1 const maxn = 100000 ;
2 key =- 219 ;
3 max = maxn * 2 ;
4 var l,r,ls,rs,c,d,s: array [ 1 ..max] of longint;
5 v: array [ 1 ..maxn] of longint;
6 m,k,i,tt,x,y,z:longint;
7 ch,ignore:char;
8 procedure down(x:longint);
9 begin
10 if c[x] <> key
11 then begin
12 s[x]: = c[x] * (r[x] - l[x]);
13 if ls[x] <> 0
14 then begin
15 c[ls[x]]: = c[x];
16 d[ls[x]]: = 0 ;
17 end ;
18 if rs[x] <> 0
19 then begin
20 c[rs[x]]: = c[x];
21 d[rs[x]]: = 0 ;
22 end ;
23 c[x]: = key;
24 end
25 else if d[x] <> 0
26 then begin
27 s[x]: = s[x] + d[x] * (r[x] - l[x]);
28 if ls[x] <> 0 then if c[ls[x]] <> key
29 then c[ls[x]]: = c[ls[x]] + d[x]
30 else d[ls[x]]: = d[ls[x]] + d[x];
31 if rs[x] <> 0 then if c[rs[x]] <> key
32 then c[rs[x]]: = c[rs[x]] + d[x]
33 else d[rs[x]]: = d[rs[x]] + d[x];
34 d[x]: = 0 ;
35 end ;
36 end ;
37 procedure update(x:longint);
38 begin
39 down(ls[x]); down(rs[x]);
40 s[x]: = s[ls[x]] + s[rs[x]];
41 end ;
42 procedure build(a,b:longint);
43 var x,mid:longint;
44 begin
45 inc(tt); x: = tt;
46 l[x]: = a; r[x]: = b;
47 d[x]: = 0 ; c[x]: = key;
48 if b - a = 1
49 then s[x]: = v[b]
50 else begin
51 mid: = (a + b)shr 1 ;
52 ls[x]: = tt + 1 ; build(a,mid);
53 rs[x]: = tt + 1 ; build(mid,b);
54 s[x]: = s[ls[x]] + s[rs[x]];
55 end ;
56 end ;
57 procedure cover(x,a,b,v:longint);
58 var mid:longint;
59 begin
60 down(x);
61 if (a <= l[x]) and (r[x] <= b)
62 then begin c[x]: = v; d[x]: = 0 ; end
63 else begin
64 mid: = (l[x] + r[x])shr 1 ;
65 if a < mid then cover(ls[x],a,b,v);
66 if b > mid then cover(rs[x],a,b,v);
67 update(x);
68 end ;
69 end ;
70 procedure insert(x,a,b,v:longint);
71 var mid:longint;
72 begin
73 down(x);
74 if (a <= l[x]) and (r[x] <= b)
75 then if c[x] <> key
76 then c[x]: = c[x] + v
77 else d[x]: = d[x] + v
78 else begin
79 mid: = (l[x] + r[x])shr 1 ;
80 if a < mid then insert(ls[x],a,b,v);
81 if b > mid then insert(rs[x],a,b,v);
82 update(x);
83 end ;
84 end ;
85 function sum(x,a,b:longint):longint;
86 var mid:longint;
87 begin
88 down(x);
89 if (a <= l[x]) and (r[x] <= b)
90 then sum: = s[x]
91 else begin
92 sum: = 0 ;
93 mid: = (l[x] + r[x])shr 1 ;
94 if a < mid then sum: = sum + sum(ls[x],a,b);
95 if b > mid then sum: = sum + sum(rs[x],a,b);
96 update(x);
97 end ;
98 end ;
99 begin
100 assign(input, ' paint.in ' ); reset(input);
101 assign(output, ' paint.out ' ); rewrite(output);
102 readln(m,k);
103 for i: = 1 to m do
104 read(v[i]);
105 readln;
106 tt: = 0 ;
107 build( 0 ,m);
108 for i: = 1 to k do
109 begin
110 read(ch);
111 repeat read(ignore); until ignore = ' ' ;
112 read(x,y);
113 if ch <> ' S ' then read(z);
114 readln;
115 case ch of
116 ' C ' :cover( 1 ,x - 1 ,y,z);
117 ' A ' :insert( 1 ,x - 1 ,y,z);
118 ' S ' :writeln(sum( 1 ,x - 1 ,y));
119 end ;
120 end ;
121 close(input); close(output);
122 end .
123

 

 

转载于:https://www.cnblogs.com/Booble/archive/2010/10/11/1847793.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值