
目录
[二、基础进阶:无修改的多信息维护 ------ 区间最小值查询](#二、基础进阶:无修改的多信息维护 —— 区间最小值查询)
[2.1 例题:忠诚(洛谷 P1816)](#2.1 例题:忠诚(洛谷 P1816))
[完整 C++ 代码](#完整 C++ 代码)
[三、进阶核心:带修改的多信息维护 ------ 懒标记的配合](#三、进阶核心:带修改的多信息维护 —— 懒标记的配合)
[3.1 例题 1:开关(洛谷 P3870 [TJOI2009])](#3.1 例题 1:开关(洛谷 P3870 [TJOI2009]))
[步骤 1:结构体设计](#步骤 1:结构体设计)
[步骤 2:pushup 实现](#步骤 2:pushup 实现)
[步骤 3:懒标记处理](#步骤 3:懒标记处理)
[步骤 4:修改与查询逻辑](#步骤 4:修改与查询逻辑)
[完整 C++ 代码](#完整 C++ 代码)
[3.2 例题 2:贪婪大陆(洛谷 P2184)](#3.2 例题 2:贪婪大陆(洛谷 P2184))
[步骤 1:结构体设计](#步骤 1:结构体设计)
[步骤 2:pushup 实现](#步骤 2:pushup 实现)
[步骤 3:操作逻辑](#步骤 3:操作逻辑)
[完整 C++ 代码](#完整 C++ 代码)
[四、高阶挑战:复杂修改的多信息维护 ------ 等差数列加成](#四、高阶挑战:复杂修改的多信息维护 —— 等差数列加成)
[4.1 例题:无聊的数列(洛谷 P1438)](#4.1 例题:无聊的数列(洛谷 P1438))
[解法 1:直接维护等差数列信息](#解法 1:直接维护等差数列信息)
[步骤 1:结构体设计](#步骤 1:结构体设计)
[步骤 2:pushup 实现](#步骤 2:pushup 实现)
[步骤 3:懒标记处理(核心难点)](#步骤 3:懒标记处理(核心难点))
[步骤 4:修改与查询逻辑](#步骤 4:修改与查询逻辑)
[解法 1 完整 C++ 代码](#解法 1 完整 C++ 代码)
[解法 2:差分 + 线段树(简化版)](#解法 2:差分 + 线段树(简化版))
[解法 2 核心代码(片段)](#解法 2 核心代码(片段))
[5.1 通用 C++ 模板(含区间修改 + 区间查询)](#5.1 通用 C++ 模板(含区间修改 + 区间查询))
[5.2 模板使用说明](#5.2 模板使用说明)
前言
在算法学习中,线段树绝对是当之无愧的万能数据结构 ,它凭借分治思想和高效的区间操作能力,能轻松应对单点修改、区间查询、区间修改等经典问题。但实际刷题时,我们遇到的场景远不止维护区间和、最大值这么简单 ------ 可能需要统计区间亮灯数、计算不同地雷种类、维护等差数列加成,甚至是处理 01 序列的连续最长子段。这时候,线段树维护更多类型信息的能力就成了解题的关键。
本文就带大家从基础出发,深入探索线段树如何灵活维护各类复杂信息,从结构体设计、懒标记处理到具体例题实战,一步步拆解核心思路,让你彻底掌握线段树的进阶用法!下面就让我们正式开始吧!

一、核心思考:维护多类型信息的三大关键
线段树的本质是用二叉树维护区间信息,基础的区间和、最大值维护,只需要在结构体中定义单个变量,配合简单的pushup整合左右孩子信息即可。但要维护更复杂的信息,核心要解决三个问题,这也是贯穿所有进阶题型的通用思路:
- 结构体该存什么? :根据问题需求,确定需要维护的所有区间信息,比如统计连续 1 的长度,需要维护区间最长连续 1、左端点开始最长连续 1、右端点结束最长连续 1等;
- 父节点信息如何来? :实现
pushup函数,明确如何通过左右孩子的信息,推导出父节点的完整信息,这是分治思想的核心体现;- 懒标记该怎么发? :如果涉及区间修改,需要设计对应的懒标记,明确标记的含义、如何下放(
pushdown)、如何更新子节点的信息,确保修改操作的延迟执行不影响结果。
简单来说,只要想清楚这三个问题,无论多复杂的信息维护,都能套上线段树的框架解决。接下来,我们通过经典例题,从易到难拆解这些思路的实际应用。
二、基础进阶:无修改的多信息维护 ------ 区间最小值查询
先从无修改操作 的场景入手,帮大家建立 "按需定义结构体" 的思维。这类问题不需要懒标记,核心是设计结构体和实现pushup,属于维护多信息的入门题型。
2.1 例题:忠诚(洛谷 P1816)
题目链接:https://www.luogu.com.cn/problem/P1816

题目要求
给定 m 笔账目,多次查询区间[a,b]内的最小值,无修改操作。
核心分析
- 结构体设计 :需要维护区间的左右边界
l/r,以及当前区间的最小值min;- pushup 实现:父节点的最小值 = 左孩子最小值 和 右孩子最小值 中的较小值;
- 查询逻辑:基础的区间拆分拼凑,完全覆盖则返回最小值,否则递归左右孩子取最小。
完整 C++ 代码
cpp
#include <iostream>
#include <algorithm>
using namespace std;
#define lc p << 1 // 左孩子节点编号
#define rc p << 1 | 1 // 右孩子节点编号
const int N = 1e5 + 10;
const int INF = 1e9;
int n, m;
int a[N]; // 原始数组
// 线段树节点结构体:维护区间左右边界 + 区间最小值
struct node
{
int l, r, min;
}tr[N << 2]; // 线段树空间开4倍
// 构建线段树
void build(int p, int l, int r)
{
tr[p] = {l, r, a[l]};
if (l == r) return; // 叶子节点,直接返回
int mid = (l + r) >> 1;
build(lc, l, mid); // 构建左子树
build(rc, mid + 1, r); // 构建右子树
// 整合左右孩子信息:父节点最小值 = 左右孩子最小值的较小值
tr[p].min = min(tr[lc].min, tr[rc].min);
}
// 区间查询最小值:查询[x,y]的最小值
int query(int p, int x, int y)
{
int l = tr[p].l, r = tr[p].r;
if (x <= l && r <= y) return tr[p].min; // 完全覆盖,直接返回
int mid = (l + r) >> 1;
int res = INF;
if (x <= mid) res = min(res, query(lc, x, y)); // 左孩子有重叠,递归查询
if (y > mid) res = min(res, query(rc, x, y)); // 右孩子有重叠,递归查询
return res;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cin >> n >> m;
for (int i = 1; i <= n; i++) cin >> a[i];
build(1, 1, n); // 根节点编号为1,维护[1,n]
while (m--)
{
int a, b;
cin >> a >> b;
cout << query(1, a, b) << " ";
}
return 0;
}
关键总结
这道题是基础的多信息维护入门,虽然只维护了一个 "最小值",但核心思路是按需定义结构体 。如果题目要求查询区间最大值 + 最小值,只需要在结构体中增加max变量,pushup时同时更新min和max即可,框架完全不变。
三、进阶核心:带修改的多信息维护 ------ 懒标记的配合
实际刷题中,绝大多数进阶题型都包含区间修改 + 区间查询 ,此时需要在 "结构体设计 + pushup" 的基础上,增加懒标记的设计和 pushdown 的实现。这也是线段树维护多信息的难点,我们通过两个经典例题,拆解不同类型懒标记的处理思路。
3.1 例题 1:开关(洛谷 P3870 [TJOI2009])
题目链接:https://www.luogu.com.cn/problem/P3870

题目要求
有 n 盏初始关闭的灯,支持两种操作:
- 区间
[a,b]翻转灯的状态(开→关,关→开);- 查询区间
[a,b]内打开的灯的数量。
核心分析
这道题需要维护区间亮灯数 ,同时处理区间翻转 的修改操作,核心是设计翻转懒标记,并明确标记下放时如何更新子节点的亮灯数。
步骤 1:结构体设计
需要维护的信息:
- 区间左右边界
l/r;- 区间亮灯数量
sum;- **懒标记
cnt:**记录区间翻转的次数(奇数表示需要翻转,偶数表示无需翻转)。
步骤 2:pushup 实现
父节点的亮灯数 = 左孩子亮灯数 + 右孩子亮灯数,这是基础的求和整合,难度不大。
步骤 3:懒标记处理
- lazy 函数:接收翻转次数,更新当前节点的亮灯数(翻转后亮灯数 = 区间长度 - 原亮灯数),并累加懒标记;
- pushdown 函数:将当前节点的翻转标记下放给左右孩子,然后清空当前节点的标记,确保后续递归时子节点的信息是最新的。
步骤 4:修改与查询逻辑
- 修改 :完全覆盖则执行
lazy翻转,否则先pushdown下放标记,再递归左右孩子,最后pushup整合信息;- 查询 :完全覆盖则返回亮灯数,否则先
pushdown,再递归左右孩子求和。
完整 C++ 代码
cpp
#include <iostream>
using namespace std;
#define lc p << 1
#define rc p << 1 | 1
const int N = 1e5 + 10;
int n, m;
// 线段树节点:l/r=区间边界,sum=亮灯数,cnt=翻转懒标记(翻转次数)
struct node
{
int l, r, sum, cnt;
}tr[N << 2];
// 懒标记更新:当前节点翻转cnt次
void lazy(int p, int cnt)
{
if (cnt % 2 == 1) // 奇数翻转才会改变状态
tr[p].sum = tr[p].r - tr[p].l + 1 - tr[p].sum;
tr[p].cnt += cnt; // 累加翻转次数
}
// 整合左右孩子信息
void pushup(int p)
{
tr[p].sum = tr[lc].sum + tr[rc].sum;
}
// 下放懒标记给左右孩子
void pushdown(int p)
{
if (tr[p].cnt == 0) return; // 无标记,无需下放
lazy(lc, tr[p].cnt);
lazy(rc, tr[p].cnt);
tr[p].cnt = 0; // 清空当前节点标记
}
// 构建线段树:初始所有灯关闭,sum=0,cnt=0
void build(int p, int l, int r)
{
tr[p] = {l, r, 0, 0};
if (l == r) return;
int mid = (l + r) >> 1;
build(lc, l, mid);
build(rc, mid + 1, r);
pushup(p);
}
// 区间修改:翻转[x,y]的灯状态
void modify(int p, int x, int y)
{
int l = tr[p].l, r = tr[p].r;
if (x <= l && r <= y)
{
lazy(p, 1); // 翻转1次
return;
}
pushdown(p); // 先下放标记
int mid = (l + r) >> 1;
if (x <= mid) modify(lc, x, y);
if (y > mid) modify(rc, x, y);
pushup(p); // 整合左右孩子信息
}
// 区间查询:查询[x,y]的亮灯数
int query(int p, int x, int y)
{
int l = tr[p].l, r = tr[p].r;
if (x <= l && r <= y) return tr[p].sum;
pushdown(p); // 先下放标记
int mid = (l + r) >> 1;
int res = 0;
if (x <= mid) res += query(lc, x, y);
if (y > mid) res += query(rc, x, y);
return res;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cin >> n >> m;
build(1, 1, n);
while (m--)
{
int op, a, b;
cin >> op >> a >> b;
if (op == 0) modify(1, a, b); // 翻转
else cout << query(1, a, b) << endl; // 查询
}
return 0;
}
关键总结
这道题的懒标记是计数型 ,核心是根据标记的奇偶性 更新信息。这类懒标记的特点是:标记的累加会改变操作的效果,需要根据实际逻辑判断是否执行更新。同时,pushdown 的时机是关键 ------ 所有递归操作(modify/query)前,只要不是叶子节点,都要先判断是否有懒标记,有则下放,确保子节点信息准确。
3.2 例题 2:贪婪大陆(洛谷 P2184)
题目链接:https://www.luogu.com.cn/problem/P2184

题目要求
给定长度为 n 的战壕,支持两种操作:
- 区间
[l,r]布置一种新地雷(每种地雷唯一);- 查询区间
[l,r]内有多少种不同的地雷。
核心分析
这道题的难点在于如何统计区间内的地雷种类 ,直接维护种类数会非常复杂,需要通过转化问题来简化信息维护:
核心转化 :对于一次布置[L,R]的地雷,相当于在L位置加一个起点标记 ,在R位置加一个终点标记 。查询[l,r]的地雷种类数 = [1,r]的起点数 - [1,l-1]的终点数。
通过这个转化,问题就变成了维护区间内的起点数和终点数,属于基础的区间查询 + 单点修改,无需复杂的懒标记,大大降低了难度。
步骤 1:结构体设计
需要维护区间左右边界l/r,以及两个计数:cnt[0](起点数)、cnt[1](终点数)。
步骤 2:pushup 实现
父节点的起点数 = 左孩子起点数 + 右孩子起点数,终点数同理,简单的求和整合。
步骤 3:操作逻辑
- 布置地雷 :对
L位置执行单点修改,cnt[0]++;对R位置执行单点修改,cnt[1]++;- 查询种类 :计算**query(1,1,r,0) - query(1,1,l-1,1)**即可。
完整 C++ 代码
cpp
#include <iostream>
using namespace std;
#define lc p << 1
#define rc p << 1 | 1
const int N = 1e5 + 10;
int n, m;
// 线段树节点:cnt[0]=起点数,cnt[1]=终点数
struct node
{
int l, r, cnt[2];
}tr[N << 2];
// 整合左右孩子信息
void pushup(int p)
{
tr[p].cnt[0] = tr[lc].cnt[0] + tr[rc].cnt[0];
tr[p].cnt[1] = tr[lc].cnt[1] + tr[rc].cnt[1];
}
// 构建线段树:初始起点数和终点数都为0
void build(int p, int l, int r)
{
tr[p] = {l, r, 0, 0};
if (l == r) return;
int mid = (l + r) >> 1;
build(lc, l, mid);
build(rc, mid + 1, r);
pushup(p);
}
// 单点修改:在x位置,k类型(0=起点,1=终点)计数+1
void modify(int p, int x, int k)
{
int l = tr[p].l, r = tr[p].r;
if (l == x && r == x)
{
tr[p].cnt[k]++;
return;
}
int mid = (l + r) >> 1;
if (x <= mid) modify(lc, x, k);
else modify(rc, x, k);
pushup(p);
}
// 区间查询:查询[x,y]内k类型的计数
int query(int p, int x, int y, int k)
{
int l = tr[p].l, r = tr[p].r;
if (x <= l && r <= y) return tr[p].cnt[k];
int mid = (l + r) >> 1;
int res = 0;
if (x <= mid) res += query(lc, x, y, k);
if (y > mid) res += query(rc, x, y, k);
return res;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cin >> n >> m;
build(1, 1, n);
while (m--)
{
int op, l, r;
cin >> op >> l >> r;
if (op == 1)
{
// 布置地雷:l加起点,r加终点
modify(1, l, 0);
modify(1, r, 1);
}
else
{
// 查询种类:[1,r]起点数 - [1,l-1]终点数
int ans = query(1, 1, r, 0) - query(1, 1, l - 1, 1);
cout << ans << endl;
}
}
return 0;
}
关键总结
这道题的核心不是线段树的语法,而是问题的转化能力 。很多时候,直接维护题目要求的信息会非常复杂,需要通过数学推导、逻辑转化,将问题转化为线段树能轻松维护的信息(如计数、求和、最值)。这也是算法学习的核心 ------先转化问题,再选择数据结构。
四、高阶挑战:复杂修改的多信息维护 ------ 等差数列加成
前面的例题都是简单的区间修改(翻转、单点计数),接下来的无聊的数列(洛谷 P1438) 涉及区间等差数列加成 ,属于更复杂的区间修改,需要设计维护等差数列信息的懒标记 ,同时处理标记下放时的首项偏移问题,是维护多信息的高阶题型。
4.1 例题:无聊的数列(洛谷 P1438)
题目链接:https://www.luogu.com.cn/problem/P1438

题目要求
维护一个数列,支持两种操作:
- 区间
[l,r]加上一个等差数列,首项为 K,公差为 D(即a[l]+K,a[l+1]+K+D,...,a[r]+K+(r-l)*D);- 单点查询
a[p]的值。
核心分析
这道题有两种解法,我们重点讲解直接维护等差数列信息 的解法,更能体现线段树维护复杂信息的能力;同时补充差分 + 线段树的解法,供大家对比学习。
解法 1:直接维护等差数列信息
步骤 1:结构体设计
需要维护的信息:
- 区间左右边界
l/r;- 区间和
sum:用于单点查询(叶子节点的 sum 就是对应位置的值);- 懒标记
k和d:分别表示当前区间需要加上的等差数列的首项 和公差。
步骤 2:pushup 实现
父节点的区间和 = 左孩子区间和 + 右孩子区间和,基础求和逻辑。
步骤 3:懒标记处理(核心难点)
- lazy 函数 :接收等差数列的首项
k和公差d,根据等差数列求和公式更新当前节点的区间和:sum += (首项+末项)项数/2,其中末项 =k + (区间长度-1)d,项数 =r-l+1;同时累加懒标记k和d。- pushdown 函数 :下放懒标记时,右孩子的首项需要偏移 ------ 左孩子的首项就是父节点的
k,公差d;右孩子的*首项 =k + (右孩子左端点 - 父节点左端点)d,公差仍为d。下放后清空父节点的懒标记。
步骤 4:修改与查询逻辑
- 修改 :完全覆盖则执行
lazy更新,否则先pushdown,再递归左右孩子,最后pushup;- 查询 :单点查询,递归到叶子节点返回
sum即可,过程中需要pushdown下放标记。
解法 1 完整 C++ 代码
cpp
#include <iostream>
using namespace std;
#define lc p << 1
#define rc p << 1 | 1
typedef long long LL; // 防止溢出
const int N = 1e5 + 10;
int n, m;
int a[N];
// 线段树节点:sum=区间和,k=等差数列首项懒标记,d=公差懒标记
struct node
{
int l, r;
LL sum, k, d;
}tr[N << 2];
// 整合左右孩子信息
void pushup(int p)
{
tr[p].sum = tr[lc].sum + tr[rc].sum;
}
// 懒标记更新:当前区间加上首项k,公差d的等差数列
void lazy(int p, LL k, LL d)
{
int l = tr[p].l, r = tr[p].r;
int len = r - l + 1;
LL last = k + (len - 1) * d; // 等差数列末项
tr[p].sum += (k + last) * len / 2; // 等差数列求和
tr[p].k += k;
tr[p].d += d;
}
// 下放懒标记:核心处理右孩子的首项偏移
void pushdown(int p)
{
if (tr[p].k == 0 && tr[p].d == 0) return;
int l = tr[p].l, r = tr[p].r;
int mid = (l + r) >> 1;
// 左孩子:首项=tr[p].k,公差=tr[p].d
lazy(lc, tr[p].k, tr[p].d);
// 右孩子:首项偏移 = tr[p].k + (mid+1 - l)*tr[p].d
lazy(rc, tr[p].k + (mid + 1 - l) * tr[p].d, tr[p].d);
// 清空父节点标记
tr[p].k = 0;
tr[p].d = 0;
}
// 构建线段树
void build(int p, int l, int r)
{
tr[p] = {l, r, a[l], 0, 0};
if (l == r) return;
int mid = (l + r) >> 1;
build(lc, l, mid);
build(rc, mid + 1, r);
pushup(p);
}
// 区间修改:[x,y]加上首项k,公差d的等差数列
void modify(int p, int x, int y, LL k, LL d)
{
int l = tr[p].l, r = tr[p].r;
if (x <= l && r <= y)
{
// 当前区间的首项需要偏移:k + (l - x)*d
lazy(p, k + (l - x) * d, d);
return;
}
pushdown(p);
int mid = (l + r) >> 1;
if (x <= mid) modify(lc, x, y, k, d);
if (y > mid) modify(rc, x, y, k, d);
pushup(p);
}
// 单点查询:查询x位置的值
LL query(int p, int x)
{
int l = tr[p].l, r = tr[p].r;
if (l == x && r == x) return tr[p].sum;
pushdown(p);
int mid = (l + r) >> 1;
if (x <= mid) return query(lc, x);
else return query(rc, x);
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cin >> n >> m;
for (int i = 1; i <= n; i++) cin >> a[i];
build(1, 1, n);
while (m--)
{
int op;
cin >> op;
if (op == 1)
{
int l, r;
LL k, d;
cin >> l >> r >> k >> d;
modify(1, l, r, k, d);
}
else
{
int p;
cin >> p;
cout << query(1, p) << endl;
}
}
return 0;
}
解法 2:差分 + 线段树(简化版)
核心转化 :将区间等差数列加成,转化为三次区间单点修改,用基础的区间和线段树即可实现,无需复杂的懒标记。
- 设差分数组为
diff,原始数组a[i] = diff[1]+diff[2]+...+diff[i]; - 区间
[l,r]加首项 K、公差 D 的等差数列,等价于:- diff[l] += K;
- diff[l+1...r] += D;
- diff[r+1] -= (K + (r-l)*D);
- 单点查询
a[p],等价于查询差分数组[1,p]的区间和。
这种解法通过差分将复杂的等差数列修改,转化为线段树的基础操作,代码更简单,适合对懒标记掌握不熟练的同学。
解法 2 核心代码(片段)
cpp
// 差分数组的线段树,维护区间和+区间加懒标记(基础版)
void modify(int p, int x, int y, LL add)
{
int l = tr[p].l, r = tr[p].r;
if (x <= l && r <= y)
{
tr[p].sum += add * (r - l + 1);
tr[p].add += add;
return;
}
pushdown(p);
int mid = (l + r) >> 1;
if (x <= mid) modify(lc, x, y, add);
if (y > mid) modify(rc, x, y, add);
pushup(p);
}
// 主函数中的修改操作
if (op == 1)
{
int l, r;
LL k, d;
cin >> l >> r >> k >> d;
modify(1, l, l, k); // 第一步
if (l+1 <= r) modify(1, l+1, r, d); // 第二步
if (r+1 <= n) modify(1, r+1, r+1, -(k + (r-l)*d)); // 第三步
}
关键总结
这道题的两种解法体现了线段树的灵活性:
- 直接维护复杂信息: 适合锻炼懒标记的设计和处理能力,核心是解决标记下放的偏移问题;
- **转化为基础问题:**通过差分、数学推导等方式,将复杂操作转化为线段树的基础操作,降低编码难度。
实际刷题中,建议先尝试转化问题,如果转化不了,再考虑直接维护复杂信息。
五、通用解题框架:维护任意信息的线段树模板
通过上面的例题,我们可以总结出维护任意类型信息的线段树通用框架 ,无论题目要求维护什么信息,都可以套这个框架,只需要根据需求修改结构体、pushup、lazy、pushdown四个部分,其余代码基本不变。
5.1 通用 C++ 模板(含区间修改 + 区间查询)
cpp
#include <iostream>
using namespace std;
#define lc p << 1
#define rc p << 1 | 1
typedef long long LL;
const int N = 1e5 + 10;
int n, m;
int a[N]; // 原始数组
// 步骤1:根据问题需求,定义结构体(维护的信息+懒标记)
struct node
{
int l, r;
// ******** 自定义维护的信息 ********
LL sum; // 示例:区间和
// ******** 自定义懒标记 ********
LL add; // 示例:区间加懒标记
}tr[N << 2];
// 步骤2:实现pushup------整合左右孩子信息到父节点
void pushup(int p)
{
// ******** 自定义整合逻辑 ********
tr[p].sum = tr[lc].sum + tr[rc].sum;
}
// 步骤3:实现lazy------用懒标记更新当前节点信息
void lazy(int p, LL val)
{
// ******** 自定义懒标记更新逻辑 ********
int len = tr[p].r - tr[p].l + 1;
tr[p].sum += val * len;
tr[p].add += val;
}
// 步骤4:实现pushdown------下放懒标记给左右孩子
void pushdown(int p)
{
// ******** 自定义标记下放逻辑 ********
if (tr[p].add == 0) return;
lazy(lc, tr[p].add);
lazy(rc, tr[p].add);
tr[p].add = 0; // 清空当前节点标记
}
// 构建线段树(基本不变)
void build(int p, int l, int r)
{
// ******** 初始化结构体 ********
tr[p] = {l, r, a[l], 0};
if (l == r) return;
int mid = (l + r) >> 1;
build(lc, l, mid);
build(rc, mid + 1, r);
pushup(p);
}
// 区间修改(基本不变,仅修改传入参数)
void modify(int p, int x, int y, LL val)
{
int l = tr[p].l, r = tr[p].r;
if (x <= l && r <= y)
{
lazy(p, val);
return;
}
pushdown(p);
int mid = (l + r) >> 1;
if (x <= mid) modify(lc, x, y, val);
if (y > mid) modify(rc, x, y, val);
pushup(p);
}
// 区间查询(基本不变,仅修改返回值和查询逻辑)
LL query(int p, int x, int y)
{
int l = tr[p].l, r = tr[p].r;
if (x <= l && r <= y)
{
// ******** 自定义返回值 ********
return tr[p].sum;
}
pushdown(p);
int mid = (l + r) >> 1;
LL res = 0; // ******** 初始化查询结果 ********
if (x <= mid) res += query(lc, x, y); // ******** 自定义查询逻辑 ********
if (y > mid) res += query(rc, x, y); // ******** 自定义查询逻辑 ********
return res;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cin >> n >> m;
for (int i = 1; i <= n; i++) cin >> a[i];
build(1, 1, n);
while (m--)
{
int op, x, y;
LL val;
cin >> op >> x >> y;
if (op == 1)
{
cin >> val;
modify(1, x, y, val);
}
else
{
cout << query(1, x, y) << endl;
}
}
return 0;
}
5.2 模板使用说明
- 结构体 :删除示例的
sum和add,添加题目需要维护的信息(如min/max、lmax/rmax、cnt[2])和对应的懒标记(如cnt、k/d、rev);- pushup:根据左右孩子的信息,写出父节点信息的推导公式,这是最核心的部分;
- lazy:根据懒标记的含义,写出当前节点信息的更新逻辑,注意累加 / 覆盖懒标记;
- pushdown:将当前节点的懒标记下放给左右孩子,更新孩子的信息,然后清空当前节点的标记;
- build/modify/query:除了初始化、参数传入、查询结果整合外,其余代码基本不变,只需根据需求微调。
六、常见易错点总结
在维护多类型信息的线段树编码中,新手很容易出现各种错误,这里总结了五大高频易错点,帮大家避坑:
- 懒标记只存不更:只修改了懒标记,但没有更新当前节点的信息,导致后续查询结果错误;
- pushdown 时机缺失:在 modify/query 前没有执行 pushdown,子节点的信息还是旧的,查询 / 修改结果错误;
- pushdown 后未清空标记:下放标记后没有将父节点的懒标记置为初始值,导致标记被重复下放,多次更新;
- 空间开太小 :线段树的空间必须开原始数组的 4 倍,否则会出现数组越界,程序崩溃;
- 数据溢出 :维护区间和、等差数列和等信息时,一定要用
long long类型,避免 int 溢出。
总结
线段树作为算法竞赛中的 "万能工具",掌握了其维护多类型信息的能力,就能应对 80% 以上的区间操作问题。希望本文能帮大家建立清晰的思路,在刷题中逐步吃透线段树的进阶用法!