#线段树,懒标记#JZOJ 1960 最大值2

线段树与懒标记
本文介绍了一种使用线段树解决区间更新和查询问题的方法,并通过懒标记优化操作效率。代码实现包括线段树的构建、更新及查找功能。

线段树需要用懒标记


代码

#include <cstdio>
#include <cctype>
#include <climits>
#include <algorithm>
using namespace std;
struct node{long long w,lazy;}tree[400001]; int a[100001],n,m; long long ans;
inline int in(){
	int ans=0,f=1; char c=getchar();
    while (!isdigit(c)&&c!='-') c=getchar();
    if (c=='-') c=getchar(),f=-f;
    while (isdigit(c)) ans=ans*10+c-48,c=getchar();
    return ans*f;
}
int build(int l,int r,int k){
	if (l==r) return tree[k].w=a[r];
	return tree[k].w=max(build(l,(l+r)>>1,k<<1),build(((l+r)>>1)+1,r,(k<<1)+1));
}
inline void in1(int &x,int &y){x=in(); y=in();}
void update(int l,int r,int x,int y,int res,int k){
	if (x==l&&y==r) {tree[k].w+=res; tree[k].lazy+=res; return;}//传输懒标记
	if (tree[k].lazy){//存在懒标记就往下传
		tree[k<<1].w+=tree[k].lazy;
		tree[k<<1].lazy+=tree[k].lazy;
		tree[(k<<1)+1].w+=tree[k].lazy;
		tree[(k<<1)+1].lazy+=tree[k].lazy;
		tree[k].lazy=0;
	}
	int mid=(l+r)>>1;
	if (y<=mid) update(l,mid,x,y,res,k<<1);//左边
	else if (x>mid) update(mid+1,r,x,y,res,(k<<1)+1);//右边
	else update(l,mid,x,mid,res,k<<1),update(mid+1,r,mid+1,y,res,(k<<1)+1);//右边
	tree[k].w=max(tree[k<<1].w,tree[(k<<1)+1].w);
}
void find(int l,int r,int x,int y,int k){
	if (x==l&&y==r) {ans=max(ans,tree[k].w); return;}
	if (tree[k].lazy){//懒标记
		tree[k<<1].w+=tree[k].lazy;
		tree[k<<1].lazy+=tree[k].lazy;
		tree[(k<<1)+1].w+=tree[k].lazy;
		tree[(k<<1)+1].lazy+=tree[k].lazy;
		tree[k].lazy=0;
	}
	int mid=(l+r)>>1;
	if (y<=mid) find(l,mid,x,y,k<<1);//左边
	else if (x>mid) find(mid+1,r,x,y,(k<<1)+1);//右边
	else find(l,mid,x,mid,k<<1),find(mid+1,r,mid+1,y,(k<<1)+1);//右边
}
int main(){
	n=in();
	for (int i=1;i<=n;i++) a[i]=in();
    build(1,n,1); m=in();
    while (m--){
    	int q=in(),x,y,k; ans=INT_MIN; in1(x,y);
    	if (q==1) k=in(),update(1,n,x,y,k,1);
    	else find(1,n,x,y,1),printf("%lld\n",ans);
    }
    return 0;
}
### 线段树标记的关系 线段树是一种用于高效处理区间查询和更新的数据结构,其核心优势在于能够以 $ O(\log n) $ 的时间复杂度完成点更新、区间更新和区间查询。然而,在进行区间更新时,若直接对每个涉及的节点进行修改,会导致效率下降。为了解决这一问题,引入了“惰传播”(Lazy Propagation)技术[^1]。 标记是在线段树中实现惰传播的关键机制。它通过在节点上记录一个延迟操作的标记,避免每次更新都立即传播到所有子节点,而是在必要时才进行下传。这种延迟执行的方式显著提升了算法效率,尤其是在大规模数据和频繁更新的场景中。 --- ### 标记的原理 标记的核心思想是:**将区间更新操作延迟到真正需要访问子节点时再执行**。具体来说: - 每个线段树节点维护一个标记数组 `lazy[]`,用于记录该节点当前待处理的操作。 - 当对某个区间执行更新操作时,如果当前节点完全覆盖目标区间,则直接在该节点上更新统计值,并设置标记,表示该节点的子节点尚未同步更新。 - 只有在后续查询或更新操作需要访问子节点时,才会将当前节点的标记下传至子节点,完成实际更新操作。 这种方式保证了每次更新和查询操作的时间复杂度仍为 $ O(\log n) $,从而维持了线段树的高效性。 --- ### 标记的应用场景 标记广泛应用于以下类型的区间操作问题: 1. **区间加法/减法更新** 例如,给定一个数组,要求多次对某段区间加上一个数,并查询区间的总和。这类问题可以通过线段树结合标记高效处理。 2. **区间染色问题** 在图像处理或图形渲染中,线段树可用于管理颜色覆盖情况。标记可以记录当前区间的颜色状态,避免重复计算,提升性能。 3. **扫描线算法中的矩形面积并** 在二维几何问题中,如求多个矩形叠加后的总面积,线段树配合标记可以高效维护当前有效线段的覆盖长度[^2]。 4. **动态事件调度** 在模拟系统中,线段树可用于维护时间轴上的事件区间,标记则可用于批量更新事件状态,提高调度效率。 --- ### 标记的算法实现 以下是一个基于标记线段树实现示例,支持区间加法更新和区间求和查询: ```cpp #include <bits/stdc++.h> using namespace std; const int MAXN = 100010; long long tree[MAXN << 2], lazy[MAXN << 2]; // 下传标记 void push_down(int node, int l, int r) { if (lazy[node] != 0) { int mid = (l + r) >> 1; // 更新左子节点 tree[node * 2] += lazy[node] * (mid - l + 1); lazy[node * 2] += lazy[node]; // 更新右子节点 tree[node * 2 + 1] += lazy[node] * (r - mid); lazy[node * 2 + 1] += lazy[node]; // 清除当前节点的标记 lazy[node] = 0; } } // 区间更新 [ul, ur] 加上 val void update_range(int node, int l, int r, int ul, int ur, long long val) { if (ur < l || ul > r) return; if (ul <= l && r <= ur) { tree[node] += val * (r - l + 1); lazy[node] += val; return; } push_down(node, l, r); int mid = (l + r) >> 1; update_range(node * 2, l, mid, ul, ur, val); update_range(node * 2 + 1, mid + 1, r, ul, ur, val); tree[node] = tree[node * 2] + tree[node * 2 + 1]; } // 区间查询 [ql, qr] 的总和 long long query_range(int node, int l, int r, int ql, int qr) { if (qr < l || ql > r) return 0; if (ql <= l && r <= qr) return tree[node]; push_down(node, l, r); int mid = (l + r) >> 1; return query_range(node * 2, l, mid, ql, qr) + query_range(node * 2 + 1, mid + 1, r, ql, qr); } ``` 上述代码中,`push_down` 函数负责将当前节点的标记传递给子节点,确保子节点的值正确更新;`update_range` 和 `query_range` 分别实现了区间更新和区间查询操作。 --- ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值