一.前言

前面我们已经知道线段树能够进行单点修改和区间查询操作(基本线段树)。那么如果需要修改的是一个区间该怎么办呢?如果是暴力修改到叶子节点,复杂度即为\(O(nlog n)\),显然是十分不优秀的。那么我们能不能向区间查询一样把复杂度降到\(O(log n)\)呢?

二.算法流程

线段树肯定是兹瓷\(O(log n)\)修改的,否则发明它有何用处?所以,我我们现在需要知道,如何快速进行区间修改操作。首先,我们回顾下终止节点



假定我要在这个图上修改区间[2,8],我只要修改掉图上所有的终止节点即可。于是复杂度就成功降到\(O(log n)\)。

那么,我在终止节点上加什么东西呢?这里,我们就要引进\(Lazy\)标记。修改区间的时候,在这个区间所有的终止节点上打上一个标记,代表我这个点要加多少值。

区间查询的时候,如果访问到某个有标记的点,但又不能使用该点的值的时候,就把标记下传(有点绕,看看代码应该就明了了),其他时候直接取值就好

三.例题

1.Pku3468 A Simple Problem with Integers

Description

You have N integers, A1, A2, ... , AN. You need to deal with two kinds of operations. One type of op

eration is to add some given number to each number in a given interval. The other is to ask for the

sum of numbers in a given interval.

1.给[a ,b]整体上加上一个常数c。

2.查询[a ,b]区间的和。

Input

The first line contains two numbers N and Q. 1 ≤ N,Q ≤ 100000.

The second line contains N numbers, the initial values of A1, A2, ... , AN. -1000000000 ≤ Ai ≤ 1000000000.

Each of the next Q lines represents an operation.

"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.

Output

You need to answer all Q commands in order. One answer in a line. The sums may exceed the range of 32-bit integers

Sample Input

10 5

1 2 3 4 5 6 7 8 9 10

Q 4 4

Q 1 10

Q 2 4

C 3 6 3

Q 2 4

Sample Output

4

55

9

15

HINT

对于100%的数据:N<=100000,M<=100000

区间修改,区间查询,所以用到Lazy标记,代码里会有详细注释

#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define inf 0x7f7f7f7f
using namespace std;
typedef long long ll;
typedef unsigned int ui;
typedef unsigned long long ull;
inline int read(){
int x=0,f=1;char ch=getchar();
for (;ch<'0'||ch>'9';ch=getchar()) if (ch=='-') f=-1;
for (;ch>='0'&&ch<='9';ch=getchar()) x=(x<<1)+(x<<3)+ch-'0';
return x*f;
}
inline void print(int x){
if (x>=10) print(x/10);
putchar(x%10+'0');
}
const int N=1e5;
int n,m;
ll val[N+10];
struct Segment{
#define ls (p<<1)
#define rs (p<<1|1)
ll tree[N*4+10],cnt[N*4+10];
void updata(int p){tree[p]=(tree[ls]+tree[rs]);} //updata的内容具体情况具体分析
void add_cnt(int p,int v,int t){ //标记不影响当前节点的值,谨记这点
tree[p]=tree[p]+1ll*v*t; //t是区间长度,加标记需要乘上这个区间
cnt[p]=cnt[p]+v; //标记更新
}
void pushdown(int p,int l,int r){ //加标记下传
if (!cnt[p]) return;
int mid=(l+r)>>1;
add_cnt(ls,cnt[p],mid-l+1),add_cnt(rs,cnt[p],r-mid); //加标记的更新
cnt[p]=0;
}
void build(int p,int l,int r){
cnt[p]=0;
if (l==r){
tree[p]=val[l]; //val[i]基本是开始读入的数
return;
}
int mid=(l+r)>>1;
build(ls,l,mid),build(rs,mid+1,r);
updata(p);
}
void change(int p,int l,int r,int x,int y,int t){ //t是标记
if (x<=l&&r<=y){
add_cnt(p,t,r-l+1); //打加标记
return;
}
int mid=(l+r)>>1;
pushdown(p,l,r); //修改两边的时候记得传递标记
if (x<=mid) change(ls,l,mid,x,y,t); //访问左儿子
if (y>mid) change(rs,mid+1,r,x,y,t); //访问右儿子
updata(p); //记得更新
}
ll query(int p,int l,int r,int x,int y){
if (x<=l&&r<=y) return tree[p]; //在区间内直接输出
int mid=(l+r)>>1;
ll ans=0;
pushdown(p,l,r); //传递标记
if (x<=mid) ans=ans+query(ls,l,mid,x,y); //访问左儿子
if (y>mid) ans=ans+query(rs,mid+1,r,x,y); //访问右儿子
return ans;
}
}Tree;
char s;
int main(){
n=read(),m=read();
for (int i=1;i<=n;i++) val[i]=read();
Tree.build(1,1,n);
for (int i=1;i<=m;i++){
cin>>s;
int x=read(),y=read(),z;
if (s=='Q') printf("%lld\n",Tree.query(1,1,n,x,y));
if (s=='C') z=read(),Tree.change(1,1,n,x,y,z);
}
return 0;
}

2.

Description

给定一个正整数序列A,要求支持以下操作

1): + a b c 表示在[a,b]上加上一个常数C。

2): * a b c 在[a,b]上乘上一个常数K。

3): QUERY a b 查询[a,b]的sum。

Input

第一行两个正整数n、m,n表示序列长度,m表示操作数

第二行n个正整数,第i表示A[i]的大小

接下来的m行,每行有且仅有一种操作,具体和题目描述一致

n,m<=100000

其他权值都<=50000

Output

对于每个询问操作,输出答案对1000000007取余的结果

Sample Input

10 10

50 14 20 18 19 11 43 43 26 44

  • 3 6 41

    QUERY 1 2
  • 5 5 14

    QUERY 1 3

    QUERY 4 4

    QUERY 2 6
  • 3 5 31
  • 4 8 20
  • 5 8 28

    QUERY 6 7

Sample Output

64

125

59

260

53200

同样是区间修改,区间查询,不过本题比上题要难一些。为什么呢,因为它需要维护两个标记。

如果是两个互不相干的标记还好说,但这两个标记互相干扰!!!

因此,我们在处理这类有多个标记的题目的时候,需要考虑下这些标记互相之间的影响,标记与标记之间的先后更新顺序。

如本题,加标记与乘标记势必会互相影响

如果我们先更新加标记,那么乘标记就会变成一个实型,非常不方便。于是我们让乘标记先更新,加标记就只需要乘上一个乘标记即可,大大降低了实现难度。

(注释基本没有,不过也请读者凑合着看吧)

#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define inf 0x7f7f7f7f
using namespace std;
typedef long long ll;
typedef unsigned int ui;
typedef unsigned long long ull;
inline int read(){
int x=0,f=1;char ch=getchar();
for (;ch<'0'||ch>'9';ch=getchar()) if (ch=='-') f=-1;
for (;ch>='0'&&ch<='9';ch=getchar()) x=(x<<1)+(x<<3)+ch-'0';
return x*f;
}
inline void print(int x){
if (x>=10) print(x/10);
putchar(x%10+'0');
}
const int N=1e5,mod=1e9+7;
int n,m;
struct Segment{
#define ls (p<<1)
#define rs (p<<1|1)
int tree[N*4+10],cnt[N*4+10],res[N*4+10],size[N*4+10];
void updata(int p){tree[p]=(tree[ls]+tree[rs])%mod;}
void add_cnt(int p,int v){
tree[p]=(tree[p]+1ll*v*size[p])%mod;
cnt[p]=(cnt[p]+v)%mod;
}
void pushdown_cnt(int p){//加标记下传
if (!cnt[p]) return;
add_cnt(ls,cnt[p]),add_cnt(rs,cnt[p]);
cnt[p]=0;
}
void add_res(int p,int v){
tree[p]=1ll*tree[p]*v%mod;
res[p]=1ll*res[p]*v%mod;
cnt[p]=1ll*cnt[p]*v%mod;
}
void pushdown_res(int p){//乘标记下传
if (res[p]==1) return;
add_res(ls,res[p]),add_res(rs,res[p]);
res[p]=1;
}
void pushdown(int p){pushdown_res(p),pushdown_cnt(p);}
void build(int p,int l,int r){
cnt[p]=0,res[p]=1,size[p]=r-l+1;
if (l==r){
tree[p]=read();
return;
}
int mid=(l+r)>>1;
build(ls,l,mid),build(rs,mid+1,r);
updata(p);
}
void change(int p,int l,int r,int x,int y,int Cnt,int Res){
if (x<=l&&r<=y){
add_res(p,Res),add_cnt(p,Cnt);
return;
}
int mid=(l+r)>>1;
pushdown(p);
if (x<=mid) change(ls,l,mid,x,y,Cnt,Res);
if (y>mid) change(rs,mid+1,r,x,y,Cnt,Res);
updata(p);
}
int query(int p,int l,int r,int x,int y){
if (x<=l&&r<=y) return tree[p];
int mid=(l+r)>>1,ans=0;
pushdown(p);
if (x<=mid) ans=(ans+query(ls,l,mid,x,y))%mod;
if (y>mid) ans=(ans+query(rs,mid+1,r,x,y))%mod;
return ans;
}
}Tree;
char s[10];
int main(){
n=read(),m=read();
Tree.build(1,1,n);
for (int i=1;i<=m;i++){
scanf("%s",s+1);
int x=read(),y=read(),z;
if (s[1]=='Q') printf("%d\n",Tree.query(1,1,n,x,y));
if (s[1]=='+') z=read(),Tree.change(1,1,n,x,y,z,1);
if (s[1]=='*') z=read(),Tree.change(1,1,n,x,y,0,z);
}
return 0;
}

3.最大连续子数列和

浅谈算法——线段树之Lazy标记的更多相关文章

  1. POJ 3237 Tree (树链剖分 路径剖分 线段树的lazy标记)

    题目链接:http://poj.org/problem?id=3237 一棵有边权的树,有3种操作. 树链剖分+线段树lazy标记.lazy为0表示没更新区间或者区间更新了2的倍数次,1表示为更新,每 ...

  2. 线段树初步&&lazy标记

    线段树 一.概述: 线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点. 对于线段树中的每一个非叶子节点[a,b],它的左儿子表示的区间为[a, ...

  3. 数据结构3——浅谈zkw线段树

    线段树是所有数据结构中,最常用的之一.线段树的功能多样,既可以代替树状数组完成"区间和"查询,也可以完成一些所谓"动态RMQ"(可修改的区间最值问题)的操作.其 ...

  4. 线段树(lazy标记)-- 模板

    ], lazy[MAXN << ]; void PushUp(int rt) { ans[rt] = ans[rt << ] + ans[rt << | ]; } ...

  5. 浅谈算法和数据结构: 七 二叉查找树 八 平衡查找树之2-3树 九 平衡查找树之红黑树 十 平衡查找树之B树

    http://www.cnblogs.com/yangecnu/p/Introduce-Binary-Search-Tree.html 前文介绍了符号表的两种实现,无序链表和有序数组,无序链表在插入的 ...

  6. poj3468 线段树的懒惰标记

    题目链接:poj3468 题意:给定一段数组,有两种操作,一种是给某段区间加c,另一种是查询一段区间的和 思路:暴力的方法是每次都给这段区间的点加c,查询也遍历一遍区间,复杂度是n*n,肯定过不去,另 ...

  7. 线段树入门&lazy思想

    线段树将区间分成若干个子区间,子区间又继续分,直到区间为一个点(区间左值等于右值) 对于父区间[a,b],其子区间为[a,(a+b)/2]和[(a+b)/2+1,b] 用于求区间的值,如区间最值.区间 ...

  8. 线段树的lazy(poj3468)

    A Simple Problem with Integers Time Limit: 5000MS   Memory Limit: 131072K Total Submissions: 73163   ...

  9. HDU 4107 Gangster(线段树 特殊懒惰标记)

    两种做法. 第一种:标记区间最大值和最小值,若区间最小值>=P,则本区间+2c,若区间最大值<P,则本区间+c.非常简单的区间更新. 最后发一点牢骚:最后query查一遍就行,我这个2B竟 ...

随机推荐

  1. crontab使用简介

    crontab的配置文件: 前四行是用来配置crond任务运行的环境变量 第一行SHELL变量指定了系统要使用哪个shell,这里是bash 第二行PATH变量指定了系统执行命令的路径 第三行MAIL ...

  2. cef3的各个接口你知道几个

    CEF3基本的框架包含C/C++程 序接口,通过本地库的接口来实现,而这个库则会隔离宿主程序和 Chromium&Webkit的操作细节.它在浏览器控件和宿主程序之间提供紧密的整合,它支持用户 ...

  3. 关于rman duplicate 一些比較重要的知识点--系列三

    FYI: http://docs.oracle.com/cd/E11882_01/backup.112/e10643/rcmsynta020.htm#RCMRF126 rman duplicate d ...

  4. [外文理解] DDD创始人Eric Vans:要实现DDD原始意图,必须CQRS+Event Sourcing架构。

    原文:http://www.infoq.com/interviews/Technology-Influences-DDD# 要实现DDD(domain drive  design 领域驱动设计)原始意 ...

  5. 怎样更好的设计你的REST API之基于REST架构的Web Service设计及REST框架实现

    一.REST 含状态传输(英文:Representational State Transfer,简称REST)是Roy Fielding博士在2000年他的博士论文中提出来的一种软件架构风格. 眼下在 ...

  6. 实习生面试相关-b

    面试要准备什么 有一位小伙伴面试阿里被拒后,面试官给出了这样的评价:“……计算机基础,以及编程基础能力上都有所欠缺……”.但这种笼统的回答并非是我们希望的答案,所谓的基础到底指的是什么? 作为一名 i ...

  7. hdu4183往返经过至多每一个点一次/最大流

    题意:从s到t,每一个点有f值,仅仅能从f值小的到大的.到T后回来.仅仅能从f值大的到 小的,求可行否. 往返,事实上就是俩条路过去(每一个点最多一次).所以想到流量为2,跑最大流.看是否满2,又要每 ...

  8. 深入浅出 - Android系统移植与平台开发(十二)- Android JNI机制

    第五章.JNI机制 4.1 JNI概述 由前面基础知识可知,Android的应用层由Java语言编写,Framework框架层则是由Java代码与C/C++语言实现,之所以由两种不同的语言组合开发框架 ...

  9. 深度学习笔记之目标检测算法系列(包括RCNN、Fast RCNN、Faster RCNN和SSD)

    不多说,直接上干货! 本文一系列目标检测算法:RCNN, Fast RCNN, Faster RCNN代表当下目标检测的前沿水平,在github都给出了基于Caffe的源码. •   RCNN RCN ...

  10. c++学习笔记之基础---类内声明线程函数的调用

    近日需要将线程池封装成C++类,类名为Threadpool.在类的成员函数exec_task中调用pthread_create去启动线程执行例程thread_rounter.编译之后报错如下: spf ...