线段树维护最大子段和及其类似问题

引入

link

我们可以分析出上题就是带修改的最大子段和。

遇到这种类型的题目应该想到用线段树。

实现

对于原数列,先建起一棵线段树,每个节点包含 最大前缀、最大后缀、最大字段和、区间和 信息。

当你明确一道题是线段树时,要先思考 pushuppushdown 怎么写,因为剩下的都是差不多的。 ------ jzp.

因为本题是单查,没有 pushdown,就先考虑 pushup 怎么写:

  • 最大前缀只可能是左儿子的最大前缀或是左儿子的和加上右儿子的最大前缀,即 \(maxl_i = \max\{maxl_l, sum_l + maxl_r\}\)。
  • 最大后缀同理,\(maxr_i = \max\{maxr_r, sum_r, maxr_l\}\)。
  • 最大子段和就是左儿子最大子段和或右儿子最大子段和或左儿子最大后缀加右儿子最大前缀,即 \(maxs_i = \max\{maxs_l, maxs_r, maxr_l + maxl_r\}\)。
  • 区间和很简单,不赘述。
cpp 复制代码
void pushup(int id) {
	sum(id) = sum(ls) + sum(rs);
	maxl(id) = max(maxl(ls), sum(ls) + maxl(rs));
	maxr(id) = max(maxr(rs), sum(rs) + maxr(ls));
	maxs(id) = max(max(maxs(ls), maxs(rs)), maxr(ls) + maxl(rs));
}

那么对于每一次询问,我们找到线段树上的左右端点 \(l\)、\(r\) 对应的两点 \(p_l\)、\(p_r\)。

当我们从上往下爬树爬到 \(k = LCA(p_l, p_r)\) 时,\(l\) 和 \(r\) 就会分开为两个区间。

此时答案有几种可能:

  • \(l \le r \le m\),其中 \(m\) 为该区间的中间点,此时递归左侧得到答案。
  • \(m \lt l \le r\),此时递归右侧得到答案。
  • \(l \le m \lt r\),此时合并两次得到的答案。

以上三者取最大值返回。

这跟 cdq 分治的思想有异曲同工之妙。

当 \(l\) 与 \(r\) 并没有分叉时,就直接走下去即可。

那么此时查询也可以顺利地写出来了。

cpp 复制代码
segment query(int id, int lft, int rht, int l, int r) {	// 这里用 segment 作为返回值是因为每层递归都需要用到下一层递归的结果
	if (l <= lft && rht <= r) return seg[id];
	int mid = (lft + rht) >> 1;
	if (r <= mid) return query(ls, lft, mid, l, r);
	if (l > mid) return  query(rs, mid + 1, rht, l, r);
	segment a = query(ls, lft, mid, l, r), b = query(rs, mid + 1, rht, l, r), t;
	t.sum = a.sum + b.sum;
	t.maxl = max(a.maxl, a.sum + b.maxl);
	t.maxr = max(b.maxr, b.sum + a.maxr);
	t.maxs = max(max(a.maxs, b.maxs), a.maxr + b.maxl);
	return t;
}

整体代码:

cpp 复制代码
struct segment_tree {
	#define ls (id << 1)
	#define rs (id << 1 | 1)
	#define sum(id) seg[id].sum
	#define maxl(id) seg[id].maxl
	#define maxr(id) seg[id].maxr
	#define maxs(id) seg[id].maxs
	struct segment {
		int maxl, maxr;
		int sum, maxs;
	} seg[N << 2];
	void pushup(int id) {
		sum(id) = sum(ls) + sum(rs);
		maxl(id) = max(maxl(ls), sum(ls) + maxl(rs));
		maxr(id) = max(maxr(rs), sum(rs) + maxr(ls));
		maxs(id) = max(max(maxs(ls), maxs(rs)), maxr(ls) + maxl(rs));
	}
	void build(int id, int lft, int rht) {
		if (lft == rht) {
			sum(id) = a[lft];
			maxl(id) = maxr(id) = maxs(id) = a[lft];
			return;
		}
		int mid = (lft + rht) >> 1;
		build(ls, lft, mid), build(rs, mid + 1, rht);
		pushup(id);
	}
	void change(int id, int lft, int rht, int x, int v) {
//		if (lft > x || rht < x) return;
		if (lft == rht) {
//			a[lft] = v;
			sum(id) = v;
			maxl(id) = maxr(id) = maxs(id) = v;
			return;
		}
		int mid = (lft + rht) >> 1;
		if (x <= mid) change(ls, lft, mid, x, v); 
		else change(rs, mid + 1, rht, x, v);
		pushup(id);
	}
	segment query(int id, int lft, int rht, int l, int r) {
//		if (lft > r || rht < l) return ;
		if (l <= lft && rht <= r) return seg[id];
		int mid = (lft + rht) >> 1;
		if (r <= mid) return query(ls, lft, mid, l, r);
		if (l > mid) return  query(rs, mid + 1, rht, l, r);
		segment a = query(ls, lft, mid, l, r), b = query(rs, mid + 1, rht, l, r), t;
		t.sum = a.sum + b.sum;
		t.maxl = max(a.maxl, a.sum + b.maxl);
		t.maxr = max(b.maxr, b.sum + a.maxr);
		t.maxs = max(max(a.maxs, b.maxs), a.maxr + b.maxl);
		return t;
	}
} seg;

我们可以通过线段树维护最大子段和来推广到其他类似的问题。

相关推荐
普宁彭于晏27 分钟前
元素水平垂直居中的方法
前端·css·笔记·css3
m0_637146931 小时前
计算机网络基础总结:TCP/IP 模型、TCP vs UDP、DNS 查询过程
笔记·tcp/ip·计算机网络
Lester_11011 小时前
嵌入式学习笔记 - freeRTOS vTaskPlaceOnEventList()函数解析
笔记·学习
moxiaoran57533 小时前
uni-app学习笔记二十三--交互反馈showToast用法
笔记·学习·uni-app
scdifsn10 小时前
动手学深度学习12.7. 参数服务器-笔记&练习(PyTorch)
pytorch·笔记·深度学习·分布式计算·数据并行·参数服务器
jackson凌13 小时前
【Java学习笔记】SringBuffer类(重点)
java·笔记·学习
huangyuchi.13 小时前
【Linux】LInux下第一个程序:进度条
linux·运维·服务器·笔记·进度条·c/c++
大写-凌祁15 小时前
论文阅读:HySCDG生成式数据处理流程
论文阅读·人工智能·笔记·python·机器学习
Unpredictable22215 小时前
【VINS-Mono算法深度解析:边缘化策略、初始化与关键技术】
c++·笔记·算法·ubuntu·计算机视觉
傍晚冰川15 小时前
FreeRTOS任务调度过程vTaskStartScheduler()&任务设计和划分
开发语言·笔记·stm32·单片机·嵌入式硬件·学习