您的位置:首页 > 其它

hiho一下 第二十周(线段树区间修改+延迟标记)

2014-11-30 21:07 399 查看


题目1 : 线段树的区间修改

时间限制:10000ms
单点时限:1000ms
内存限制:256MB


描述

对于小Ho表现出的对线段树的理解,小Hi表示挺满意的,但是满意就够了么?于是小Hi将问题改了改,又出给了小Ho:
假设货架上从左到右摆放了N种商品,并且依次标号为1到N,其中标号为i的商品的价格为Pi。小Hi的每次操作分为两种可能,第一种是修改价格——小Hi给出一段区间[L, R]和一个新的价格NewP,所有标号在这段区间中的商品的价格都变成NewP。第二种操作是询问——小Hi给出一段区间[L, R],而小Ho要做的便是计算出所有标号在这段区间中的商品的总价格,然后告诉小Hi。
那么这样的一个问题,小Ho该如何解决呢?
提示:推动科学发展的除了人的好奇心之外还有人的懒惰心!


输入

每个测试点(输入文件)有且仅有一组测试数据。
每组测试数据的第1行为一个整数N,意义如前文所述。
每组测试数据的第2行为N个整数,分别描述每种商品的重量,其中第i个整数表示标号为i的商品的重量Pi。
每组测试数据的第3行为一个整数Q,表示小Hi进行的操作数。
每组测试数据的第N+4~N+Q+3行,每行分别描述一次操作,每行的开头均为一个属于0或1的数字,分别表示该行描述一个询问和一次商品的价格的更改两种情况。对于第N+i+3行,如果该行描述一个询问,则接下来为两个整数Li, Ri,表示小Hi询问的一个区间[Li, Ri];如果该行描述一次商品的价格的更改,则接下来为三个整数Li,Ri,NewP,表示标号在区间[Li, Ri]的商品的价格全部修改为NewP。
对于100%的数据,满足N<=10^5,Q<=10^5, 1<=Li<=Ri<=N,1<=Pi<=N, 0<Pi, NewP<=10^4。


输出

对于每组测试数据,对于每个小Hi的询问,按照在输入中出现的顺序,各输出一行,表示查询的结果:标号在区间[Li, Ri]中的所有商品的价格之和。

样例输入
10
4733 6570 8363 7391 4511 1433 2281 187 5166 378
6
1 5 10 1577
1 1 7 3649
0 8 10
0 1 4
1 6 8 157
1 3 4 1557


样例输出
4731
14596


思路:

此题实际为线段树的区间更新,区间更新是指更新某个区间内的叶子节点的值。这样的修改操作和之前的单点更新差别很大——之前只需要修改一个位置的值,而现在却是和询问一样,要一次性修改一个区间的值。如果仍然使用之前的方法来一个个修改的话,修改的复杂度可能会上升到O(N)呢!

为此引入了线段树中的延迟标记概念,这也是线段树的精华所在。

延迟标记:每个节点新增加一个标记,记录这个节点是否进行了某种修改(这种修改操作会影响其子节点),对于任意区间的修改,我们先按照区间查询的方式将其划分成线段树中的节点,然后修改这些节点的信息,并给这些节点标记上代表这种修改操作的标记。在修改和查询的时候,如果我们到了一个节点p,并且决定考虑其子节点,那么我们就要看节点p是否被标记,如果有,就要按照标记修改其子节点的信息,并且给子节点都标上相同的标记,同时消掉节点p的标记。

下面以修改[3, 9]区间为例——

理论上来说,如果[3, 9]这个区间被修改的话,那么下图中的所有绿色的结点的值都要得到重新计算的:



但是实际上是没有必要这么做的——我们可以引进一种叫做Lazy Tag,即延迟标记的东西——的确对于[3, 9]这样一次修改操作,我可以只去修改如下图中橙色的结点,但是在这个基础上,我要在[3, 9]分解出的4个区间[3, 3], [4, 5], [6, 8], [9, 9]所对应的结点上做一个延迟标记,表示“之前有一次操作需要将这棵子树中的所有结点的价格都进行修改,但是因为还没有用到这棵子树中的值所以我暂时不去修改”,



但是这些懒惰标记(即延迟标记,以下将不在区分二者)又有什么用呢?

其实就是一个暂时不处理,等到需要用到的时候再进行处理的思想。比如你之前说道的询问了[6, 7]这个区间,那么我再从上往下分解的时候,你会发现[6, 8]这个区间上有一个懒惰标记,那么你就应该进行一个懒惰标记的‘下放操作’——也就是说去修改[6, 8]这个结点的左右儿子的值,并且同时给左右儿子添加上新的懒惰标记,然后将[6, 8]的懒惰标记去掉。

也就是说——本来[6, 8]的左右儿子早在之前的修改操作中就需要一同进行修改的,但是因为还没有用到单独的[6, 7],[8, 8]之类的区间的原因,所以我可以暂时不处理这些结点的修改,而是用一个懒惰标记记录下来这些结点需要进行修改这件事情。然后在之后要用到这些值的时候再进行这些操作。

来看看这段伪代码:



那么通过懒惰标记的作用,就可以将修改操作也降到O(logN)的复杂度了,那么这个问题就得到完美的解决了!

代码:

#include <iostream>
#include <cstdio>
using namespace std;

const int MAXN = 1000005;
int segTree[MAXN*3];
int lazyTag[MAXN*3];

//构造线段树,算法复杂度为O(n)
void build(int node, int begin, int end){
lazyTag[node] = -1;
if(begin == end) scanf("%d", &segTree[node]);
else{
int m = (begin+end)/2;
build(2*node, begin, m);
build(2*node+1, m+1, end);
segTree[node] = segTree[2*node] + segTree[2*node+1];
}
}

void pushDown(int node, int begin, int end){
if(lazyTag[node] != -1){
int m = (begin+end)/2;

//设置左右孩子节点的标志域
lazyTag[2*node] = lazyTag[node];
lazyTag[2*node+1] = lazyTag[node];

//根据标志域设置孩子节点的值
segTree[2*node] = lazyTag[node]*(m-begin+1);
segTree[2*node+1] = lazyTag[node]*(end-m);

//传递后,当前节点标记域清空
lazyTag[node] = -1;
}
}

//单点更新, 仿照建树的样子更新
void update(int node, int begin, int end, int a, int b, int c){
if(begin==a && end==b){
lazyTag[node] = c;
segTree[node] = c*(b-a+1); //区间求和
}else{
pushDown(node, begin, end);  //延迟标记向下传递
int m = (begin+end)/2;
if(b<=m) update(2*node, begin, m, a, b, c);
else if(a>m) update(2*node+1, m+1, end, a, b, c);
else{
update(2*node, begin, m, a, m, c);
update(2*node+1, m+1, end, m+1, b, c);
}
segTree[node] = segTree[2*node] + segTree[2*node+1];
}
}

//查询[a, b]区间的和
int query(int node, int begin, int end, int a, int b){
int p1, p2;

if(a==begin && end==b) return segTree[node];
else{
pushDown(node, begin, end); //延迟标志域向下传递
int m = (begin+end)/2;
if(b<=m) return query(2*node, begin, m, a, b);
else if(a>m) return query(2*node+1, m+1, end, a, b);
else{
p1 = query(2*node, begin, m, a, m);
p2 = query(2*node+1, m+1, end, m+1, b);
return p1 + p2;
}
}
}

//查询区间的和
int main(){
int n, Q;
scanf("%d", &n);
build(1, 1, n); //必须让node从1开始

scanf("%d", &Q);
while(Q--){
int qu, a, b;
scanf("%d %d %d", &qu, &a, &b);
if(qu == 0) printf("%d\n", query(1, 1, n, a, b));
else{
int c;
scanf("%d", &c);
update(1, 1, n, a, b, c);
}
}
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: