线段树进阶(多懒标记)

不会基础先学这个线段树基础

我们以P3373 【模板】线段树 2来开始讲

简单来说题目是让我们执行3个操作:
区间加、区间乘,区间查询。
就是要用两个懒标记——一个add加法懒标记、一个mul乘法懒标记。
只有一点点与原来不一样。
更新懒标记时:
mul=mul*new_mul;
add=add*new_mul+new_add;
sum更新:
sum=sum*new_mul+(r-l+1)*new_add;
上代码

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define F(i,a,b) for(int i=a;i<=b;i++)
#define ls(x) (x<<1)
#define rs(x) (x<<1|1)
const int N=1e6+5;
int a[N],sum[N<<2],lazy_add[N<<2],lazy_mul[N<<2];
int n,q,mod,op,x,y,k;
void pushup(int k)
{
	sum[k]=sum[ls(k)]+sum[rs(k)];
	return;
}
void update_son(int k,int l,int r,int add,int mul)//懒标记下放
{
	lazy_mul[k]*=mul;
	lazy_mul[k]%=mod;
	lazy_add[k]=lazy_add[k]*mul+add;
	lazy_add[k]%=mod;
	sum[k]*=mul;
	sum[k]%=mod;
	sum[k]+=(r-l+1)*add;
	sum[k]%=mod;
	return; 
}
void pushdown(int k,int l,int r)
{
	int mid=(l+r)>>1;
	update_son(ls(k),l,mid,lazy_add[k],lazy_mul[k]);
	update_son(rs(k),mid+1,r,lazy_add[k],lazy_mul[k]);
	lazy_add[k]=0;
	lazy_mul[k]=1;
	return;
}
void build(int k,int l,int r)
{
	lazy_add[k]=0;
	lazy_mul[k]=1;
	if(l==r)
	{
		sum[k]=a[l];
		return;
	}
	int mid=(l+r)>>1;
	build(ls(k),l,mid);
	build(rs(k),mid+1,r);
	pushup(k);
	return;
}
int query(int L,int R,int k,int l,int r)
{
	if(L<=l&&r<=R)
	{
		return sum[k];
	}
	int res=0;
	int mid=(l+r)>>1;
	pushdown(k,l,r);
	if(L<=mid)
	{
		res+=query(L,R,ls(k),l,mid);
		res%=mod;
	}
	if(R>mid)
	{
		res+=query(L,R,rs(k),mid+1,r);
		res%=mod;
	}
	return res;
}
void update(int L,int R,int add,int mul,int k,int l,int r)
{
	if(L<=l&&r<=R)
	{
		update_son(k,l,r,add,mul);
		return;
	}
	int mid=(l+r)>>1;
	pushdown(k,l,r);
	if(L<=mid)
	{
		update(L,R,add,mul,ls(k),l,mid);
	}
	if(R>mid)
	{
		update(L,R,add,mul,rs(k),mid+1,r);
	}
	pushup(k);
	return;
}
signed main()
{
	cin>>n>>q>>mod;
	F(i,1,n)
	{
		cin>>a[i];
	}
	build(1,1,n);
	while(q--)
	{
		cin>>op;
		if(op==1)
		{
			cin>>x>>y>>k;
			update(x,y,0,k,1,1,n);
		}
		if(op==2)
		{
			cin>>x>>y>>k;
			update(x,y,k,1,1,1,n);
		}
		if(op==3)
		{
			cin>>x>>y;
			cout<<query(x,y,1,1,n)<<'\n';
		}
	}
	return 0;
}

练练手

P1253 扶苏的问题
简单来说题目是让我们执行3个操作:
区间加、区间赋值,区间查询。
赋值可以当成乘0加k。
完整代码

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define F(i,a,b) for(int i=a;i<=b;i++)
#define ls(x) (x<<1)
#define rs(x) (x<<1|1)
#define INF 1e18
const int N=(1e6+7);
int a[N],t[N<<2],lazy_add[N<<2],lazy_mul[N<<2];
int n,q,op,x,y,k;
void pushup(int k)
{
	t[k]=max(t[ls(k)],t[rs(k)]);
	return;
}
void update_son(int k,int l,int r,int add,int mul)
{
	lazy_mul[k]*=mul;
	lazy_add[k]=lazy_add[k]*mul+add;
	t[k]*=mul;
	t[k]+=add;
	return; 
}
void pushdown(int k,int l,int r)
{
	int mid=(l+r)>>1;
	update_son(ls(k),l,mid,lazy_add[k],lazy_mul[k]);
	update_son(rs(k),mid+1,r,lazy_add[k],lazy_mul[k]);
	lazy_add[k]=0;
	lazy_mul[k]=1;
	return;
}
void build(int k,int l,int r)
{
	lazy_add[k]=0;
	lazy_mul[k]=1;
	if(l==r)
	{
		t[k]=a[l];
		return;
	}
	int mid=(l+r)>>1;
	build(ls(k),l,mid);
	build(rs(k),mid+1,r);
	pushup(k);
	return;
}
int query(int L,int R,int k,int l,int r)
{
	if(L<=l&&r<=R)
	{
		return t[k];
	}
	int res=-1e18;
	int mid=(l+r)>>1;
	pushdown(k,l,r);
	if(L<=mid)
	{
		res=max(res,query(L,R,ls(k),l,mid));
	}
	if(R>mid)
	{
		res=max(res,query(L,R,rs(k),mid+1,r));
	}
	return res;
}
void update(int L,int R,int add,int mul,int k,int l,int r)
{
	if(L<=l&&r<=R)
	{
		update_son(k,l,r,add,mul);
		return;
	}
	int mid=(l+r)>>1;
	pushdown(k,l,r);
	if(L<=mid)
	{
		update(L,R,add,mul,ls(k),l,mid);
	}
	if(R>mid)
	{
		update(L,R,add,mul,rs(k),mid+1,r);
	}
	pushup(k);
	return;
}
signed main()
{
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	cin>>n>>q;
	F(i,1,n)
	{
		cin>>a[i];
	}
	build(1,1,n);
	while(q--)
	{
		cin>>op;
		if(op==1)
		{
			cin>>x>>y>>k;
			update(x,y,k,0,1,1,n);
		}
		if(op==2)
		{
			cin>>x>>y>>k;
			update(x,y,k,1,1,1,n);
		}
		if(op==3)
		{
			cin>>x>>y;
			cout<<query(x,y,1,1,n)<<'\n';
		}
	}
	return 0;
}
<think>嗯,用户想了解线段树的高级用法和技巧。首先,我得回忆一下线段树的基本结构。线段树是一种二叉树,用于处理区间查询和更新操作,比如区间求和、最大值、最小值等。基本实现通常包括构建、查询和更新三个部分。不过用户问的是高级用法,所以需要深入一些。 接下来,我应该考虑线段树的常见高级应用。比如延迟标记(Lazy Propagation),这在处理区间更新时非常有用,可以避免不必要的重复操作。然后还有动态开点线段树,用于处理稀疏数据或者范围非常大的情况,不需要一开始就构建完整的树结构。还有二维线段树,处理二维平面上的区间问题,不过复杂度会高一些。 另外,持久化线段树(Persistent Segment Tree)也是一个高级技巧,允许保留历史版本,适用于需要回溯的情况。比如在次更新后,还能查询之前某个版本的数据。还有区间合并操作,比如求区间最长连续子段和,这需要维护额外的信息。 然后,优化技巧方面,离散化处理可以缩小数据范围,减少内存占用。非递归实现(迭代式线段树)可能提高效率,特别是在常数因子较大的情况下。还有标记永久化,在某些情况下可以减少标记下传的次数,提升性能。 用户可能对这些技术如何具体实现感兴趣,所以需要给出代码示例。比如延迟标记的实现,动态开点的节点结构,持久化的方法等。同时,相关问题需要引导用户进一步思考,比如应用场景、与其他数据结构的比较、时间复杂度分析等。 要注意引用中的内容,比如比较型和非比较型排序算法,虽然和线段树无关,但可能在回答中提到线段树与其他数据结构的对比时,可以引用相关分类。不过用户提供的引用可能更偏向排序和机器学习,所以可能需要谨慎引用,或者仅在适当的地方提及。 最后,确保回答结构清晰,分点说明高级技巧,每个技巧有解释和代码示例,然后列出相关问题帮助用户深入学习。同时,检查LaTeX格式是否正确,行内公式用$...$,独立公式用$$...$$,并正确添加引用标识。</think>### 线段树的高级用法与技巧 线段树(Segment Tree)是一种高效处理区间查询和更新的数据结构,其高级用法主要体现在**优化时间复杂度**和**扩展功能**上。以下是关键技术与实现方法: --- #### 一、高级线段树实现 1. **延迟标记(Lazy Propagation)** 用于批量区间更新(如区间加、区间乘),避免重复递归。时间复杂度优化为$O(\log n)$[^1]。 ```python class LazySegmentTree: def __init__(self, data): self.n = len(data) self.tree = [0] * (4 * self.n) self.lazy = [0] * (4 * self.n) self.build(0, 0, self.n - 1, data) def push(self, node, left, right): if self.lazy[node] != 0: mid = (left + right) // 2 self.tree[2*node+1] += self.lazy[node] * (mid - left + 1) self.tree[2*node+2] += self.lazy[node] * (right - mid) self.lazy[2*node+1] += self.lazy[node] self.lazy[2*node+2] += self.lazy[node] self.lazy[node] = 0 ``` 2. **动态开点线段树** 适用于稀疏数据或超大范围(如值域$10^{18}$),按需创建节点: ```python class DynamicNode: def __init__(self): self.left = None self.right = None self.val = 0 self.lazy = 0 class DynamicSegmentTree: def __init__(self): self.root = DynamicNode() ``` 3. **持久化线段树(Persistent)** 保留历史版本,支持回溯查询。通过复用未修改的子树节点实现: ```python def update_persistent(node, l, r, idx, value): new_node = copy.copy(node) if l == r: new_node.val = value return new_node mid = (l + r) // 2 if idx <= mid: new_node.left = update_persistent(node.left, l, mid, idx, value) else: new_node.right = update_persistent(node.right, mid+1, r, idx, value) new_node.val = new_node.left.val + new_node.right.val return new_node ``` --- #### 二、扩展应用场景 1. **区间合并操作** 如求区间最长连续1的长度,需维护: - 左端点连续长度 - 右端点连续长度 - 最大连续长度 $$T_{\text{max}} = \max(T_{\text{left}}, T_{\text{right}}, T_{\text{left.right}} + T_{\text{right.left}})$$ 2. **二维线段树** 处理矩阵区间查询,时间复杂度$O(\log m \log n)$: ```python class SegmentTree2D: def __init__(self, matrix): self.m = len(matrix) self.n = len(matrix[0]) self.tree = [[0]*(4*self.n) for _ in range(4*self.m)] # 递归构建二维结构 ``` 3. **非对称操作支持** 混合操作如区间加+区间乘,需定义标记传递优先级: ```python def apply_lazy(node, mul, add): self.tree[node] = self.tree[node] * mul + add * (r - l + 1) self.lazy_mul[node] *= mul self.lazy_add[node] = self.lazy_add[node] * mul + add ``` --- #### 三、优化技巧 1. **离散化压缩** 将原始数据映射到紧凑区间,减少线段树深度: ```python coordinates = sorted(set(original_data)) mapping = {x:i for i, x in enumerate(coordinates)} ``` 2. **标记永久化** 适用于只查询不修改的场景(如求区间和),省去`push`操作: ```python def query_permanent(node, l, r, ql, qr, carry=0): if qr < l or ql > r: return 0 if ql <= l and r <= qr: return node.val + carry * (r - l + 1) mid = (l + r) // 2 return query_permanent(node.left, l, mid, ql, qr, carry + node.lazy) + \ query_permanent(node.right, mid+1, r, ql, qr, carry + node.lazy) ``` 3. **ZKW线段树(非递归)** 利用完全二叉树性质迭代实现,常数更优: ```python class ZKWSegmentTree: def __init__(self, data): self.N = 1 << (len(data).bit_length()) self.tree = [0] * (2 * self.N) # 初始化叶子节点... ``` ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值