算法模板——线段树之Lazy标记
来源:互联网 发布:蒙泰打印机端口设置 编辑:程序博客网 时间:2024/06/14 10:12
一.前言
前面我们已经知道线段树能够进行单点修改和区间查询操作(基本线段树)。那么如果需要修改的是一个区间该怎么办呢?如果是暴力修改到叶子节点,复杂度即为
二.算法流程
线段树肯定是兹瓷
假定我要在这个图上修改区间[2,8],我只要修改掉图上所有的终止节点即可。于是复杂度就成功降到
那么,我在终止节点上加什么东西呢?这里,我们就要引进
区间查询的时候,如果访问到某个有标记的点,但又不能使用该点的值的时候,就把标记下传(有点绕,看看代码应该就明了了),其他时候直接取值就好
三.例题
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 0x7f7f7f7fusing 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 0x7f7f7f7fusing 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标记
- poj 3468 线段树 lazy标记模板
- 线段树lazy标记
- 线段树模板(lazy标记)ZOJ 3686
- 线段树区间更新模板(lazy延迟标记)(1698)
- POJ 3667 Hotel 线段树lazy标记 (模板~~)
- 线段树模板(区间和+区间最大值 + LAZY标记)
- 线段树lazy标记??Hdu4902
- Transformation(线段树 + lazy 标记)
- 线段树lazy标记2
- 线段树区间修改之双标记 【lazy两重标记并且分类讨论】
- poj 3225 线段树注意lazy标记
- POJ 3468 线段树+lazy标记
- bzoj 1798 线段树 双lazy标记
- hdu 4578 线段树lazy标记
- 线段树区间修改 lazy标记 大法
- 线段树lazy标记1血题
- POJ3468 线段树 + Lazy Tag (延迟标记)
- 进程的环境变量
- 深入理解Java的接口和抽象类
- 【知了堂学习笔记】_JavaScript知识(二)
- Cow Contest POJ
- eclipse集成okhttp+rxjava+retrofit步骤
- 算法模板——线段树之Lazy标记
- 如何统一jdk版本
- ubuntu 安装qt5.9.3 for arm-linux am335x qt开发2
- Java web过滤器验证登录防止未登录进入界面
- VMware_centos 无法自动获取IP地址
- 决策树-构造注解树
- JNI从ByteBuffer获取数据,返回byte[]
- 《ApacheFlume教程》第八章 Flum中HDFS如何启动
- Java多线程使用场景