数据结构-线段树1(基于区间求和线段树)
🌳 线段树 学习笔记(一):基础模板与简单变式
一个算法,1题板子,1-2题上手,1-3题变式提升 ✅
📌 线段树是什么?
把数组不断二分,每个节点管一段区间,支持 O(log n) 的修改和查询。
线段树好用的一匹,只要是涉及区间求和(无论是正常运算还是二进制运算)、求区间最值、求连续最大/最小子区间,都能包办。并且单点所有操作也能搞定,简直万能。
学会它只要记住四个步骤就行:
建树 → 改区间/点 → 查询区间 → 懒标记处理复杂修改
↓ ↓ ↓ ↓
build() update() query() lazy_tag & down() & up()
接下来带你一一回忆。
1. 宏定义 与 up() 死记硬背即可
只用传递 rt。up() 只要进入了叶子节点修改了值都要加上。
#define ll long long
#define mid ((l + r) / 2)
#define lson (rt * 2)
#define rson (rt * 2 + 1)
void up(int rt)
{
sum[rt] = sum[lson] + sum[rson];
}
2. build() 三段式:叶子 → 左右建树 → up()
// 建树:从原数组a构建线段树
// 表示正在构建[l,r]的树,此树sum节点为rt
void build(ll l, ll r, ll rt)
{
if(l == r) // 来到叶子节点
{
sum[rt] = a[r]; // 叶子节点就是原数组
return;
}
build(l, mid, lson); // 建左子树
build(mid + 1, r, rson); // 建右子树
up(rt); // 当前节点和 = 左右子树相加
}
3. down() 懒标记下传
先管 son 的 lazy,再管 son 的 sum,最后清空 lazy[rt]。
它与懒标记 lazy_tag 同时出现,只有区间修改要用到懒标记时候才会出现。
// 下传懒标记(将当前节点的懒标记传递给左右儿子)
void down(int rt, int l, int r)
{
if(lazy[rt] == 0) return; // [新增] 没标记直接返回
lazy[lson] += lazy[rt];
lazy[rson] += lazy[rt];
sum[lson] += lazy[rt] * (mid - l + 1);
sum[rson] += lazy[rt] * (r - mid);
// 实际上是(r-(mid+1)+1)
lazy[rt] = 0; // 当前懒标记清零
}
4. update() 区间修改
如果包了就返回 → down() → 有左传左,有右传右 → up()
// 区间加:[L,R]统一+val
void update_add(ll L, ll R, ll val, ll l, ll r, ll rt)
{
if(L <= l and r <= R) // 如果当前区间已经被目标L,R包含
{
sum[rt] += (r - l + 1) * val; // 区间和增加了:区间长度*val
lazy[rt] += val; // 懒标记累加
return; // [新增] 注意是递归出口!
}
down(rt, l, r); // 标记下传
if(L <= mid) update_add(L, R, val, l, mid, lson);
if(R > mid) update_add(L, R, val, mid + 1, r, rson);
up(rt);
}
5. query() 区间查询
先判完全覆盖 → 直接返回;否则下传 → 递归左右,有左传左,有右传右 → 合并答案。
// 区间查询:查询[L,R]的区间和
// 在 [L,R] 中查询,当前子树为 [l,r],节点为 rt
ll query_sum(ll L, ll R, ll l, ll r, ll rt)
{
if(L <= l and r <= R) return sum[rt]; // 完全覆盖,直接返回
down(rt, l, r); // 下传懒标记
ll sum = 0;
if(L <= mid) sum += query_sum(L, R, l, mid, lson);
if(R > mid) sum += query_sum(L, R, mid + 1, r, rson);
return sum;
}
📋 已做题目回顾
| 题目 | 核心考点 |
|---|---|
| P3372 【模板】线段树 1 | 区间加 + 区间求和(基础模板) |
| P1816 忠诚 | 只查询不修改,练习query |
| P1438 无聊的数列 | 差分 + 线段树维护等差数列 |
| P3870 [TJOI2009] 开关 | 区间翻转 + 区间求和(懒标记存奇偶性) |
💡 线段树核心四函数
void build(int l, int r, int rt) // 建树
void up(int rt) // 向上合并
void down(int rt, int l, int r) // 向下传懒标记
void update(int L, int R, int val, int l, int r, int rt) // 区间修改
int query(int L, int R, int l, int r, int rt) // 区间查询
口诀:建树先叶子,修改先判全盖,不全盖就下传,递归完就合并,查询同理。💪
附:完整代码
P3372 线段树1
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
typedef long long ll;
#define mid ((l+r)/2)
#define lson ((rt*2))
#define rson ((rt*2)+1)
#define int long long
ll n,m;
ll sum[N*4],lazy[N*4],a[N];
void build(int l,int r,int rt)
{
if(l==r)
{
sum[rt]=a[r];
return;
}
build(l,mid,lson);
build(mid+1,r,rson);
sum[rt]=sum[lson]+sum[rson];
}
void down(int l,int r,int rt)
{
if(lazy[rt] == 0) return; // [新增] 没标记直接返回
lazy[lson]+=lazy[rt];
lazy[rson]+=lazy[rt];
sum[lson]+=lazy[rt]*(mid-l+1);
sum[rson]+=lazy[rt]*(r-mid);
lazy[rt]=0;
}
void update_add(int L,int R,int val,int l,int r,int rt)
{
if(L<=l and r<=R)
{
sum[rt]+=(r-l+1)*val;
lazy[rt]+=val;
return; // [新增] 注意是递归出口!
}
down(l,r,rt);
if(L<=mid) update_add(L,R,val,l,mid,lson);
if(R>mid) update_add(L,R,val,mid+1,r,rson);
sum[rt]=sum[lson]+sum[rson];
}
ll query_sum(int L,int R,int l,int r,int rt)
{
if(L<=l and r<=R) return sum[rt];
down(l,r,rt);
ll sum=0;
if(L<=mid) sum+=query_sum(L,R,l,mid,lson);
if(R>mid) sum+=query_sum(L,R,mid+1,r,rson);
return sum;
}
signed main()
{
#ifndef ONLINE_JUDGE
freopen("c.in","r",stdin);
freopen("c.out","w",stdout);
#endif
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>a[i];
build(1,n,1);
while(m--)
{
int op;
cin>>op;
if(op==1)
{
int x,y,k;
cin>>x>>y>>k;
update_add(x,y,k,1,n,1);
}
else if(op==2)
{
int x,y;
cin>>x>>y;
cout<<query_sum(x,y,1,n,1)<<endl;
}
}
return 0;
}
P1816 忠诚
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
typedef long long ll;
#define int long long
#define mid ((l+r)/2)
#define lson ((rt*2))
#define rson ((rt*2)+1)
ll minv[N*4];
ll a[N];
int n,m;
void build(int l,int r,int rt)
{
if(l==r)
{
minv[rt]=a[r];
return;
}
build(l,mid,lson);
build(mid+1,r,rson);
minv[rt]=min(minv[lson],minv[rson]);
}
ll query(int L,int R,int l,int r,int rt)
{
if(L<=l and r<=R) return minv[rt];
ll minval=1e9;
if(L<=mid) minval=min(minval,query(L,R,l,mid,lson));
if(R>mid) minval=min(minval,query(L,R,mid+1,r,rson));
return minval;
}
signed main()
{
#ifndef ONLINE_JUDGE
freopen("c.in","r",stdin);
freopen("c.out","w",stdout);
#endif
cin>>m>>n; // [新增] 注意输入顺序:m是长度,n是查询次数
for(int i=1;i<=m;i++) cin>>a[i];
build(1,m,1);
while(n--)
{
int l,r;
cin>>l>>r;
cout<<query(l,r,1,m,1)<<' ';
}
return 0;
}
P1438 无聊的数列
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
typedef long long ll;
#define int long long
#define mid ((l+r)/2)
#define lson ((rt*2))
#define rson ((rt*2)+1)
ll a[N],cf[N],sum[N*4];
ll lazy[N*4];
ll n,m;
void build(int l,int r,int rt)
{
if(l==r)
{
sum[rt]=cf[l];
return;
}
build(l,mid,lson);
build(mid+1,r,rson);
sum[rt]=sum[lson]+sum[rson];
}
void down(int l,int r,int rt)
{
if(lazy[rt] == 0) return; // [新增] 没标记直接返回
lazy[lson]+=lazy[rt];
lazy[rson]+=lazy[rt];
sum[lson]+=lazy[rt]*(mid-l+1);
sum[rson]+=lazy[rt]*(r-mid);
lazy[rt]=0;
}
void update(int L,int R,int val,int l,int r,int rt)
{
if(L>R) return; // [新增] 防止无效区间
if(L<=l and r<=R)
{
sum[rt]+=val*(r-l+1);
lazy[rt]+=val;
return;
}
down(l,r,rt);
if(L<=mid) update(L,R,val,l,mid,lson);
if(R>mid) update(L,R,val,mid+1,r,rson);
sum[rt]=sum[lson]+sum[rson];
}
ll query(int L,int R,int l,int r,int rt)
{
if(L<=l and r<=R) return sum[rt];
down(l,r,rt);
ll sum=0;
if(L<=mid) sum+=query(L,R,l,mid,lson);
if(R>mid) sum+=query(L,R,mid+1,r,rson);
return sum;
}
signed main()
{
#ifndef ONLINE_JUDGE
freopen("c.in","r",stdin);
freopen("c.out","w",stdout);
#endif
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>a[i],cf[i]=a[i]-a[i-1]; // [新增] 构造差分数组
build(1,n,1);
while(m--)
{
int opt;
cin>>opt;
if(opt==1)
{
int l,r,K,D;
cin>>l>>r>>K>>D;
update(l,l,K,1,n,1); // [新增] 首项加K
if(l+1<=r) update(l+1,r,D,1,n,1); // [新增] 公差加D
if(r+1<=n) update(r+1,r+1,-(K+(r-l)*D),1,n,1); // [新增] 差分末尾修正
}
else if(opt==2)
{
int p;
cin>>p;
cout<<query(1,p,1,n,1)<<endl; // [新增] 单点查询 -> 差分前缀和
}
}
return 0;
}
P3870 开关
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
typedef long long ll;
#define mid ((l+r)/2)
#define lson ((rt*2))
#define rson ((rt*2)+1)
int sum[N*4];
bool lazy[N*4];
int n,m;
void down(int l,int r,int rt)
{
if(lazy[rt]==0) return; // [新增] 没标记直接返回
lazy[lson]^=lazy[rt]; // [新增] 翻转标记用异或
lazy[rson]^=lazy[rt];
sum[lson]=(mid-l+1)-sum[lson]; // [新增] 区间翻转:1的个数变成长度减原个数
sum[rson]=(r-mid)-sum[rson];
lazy[rt]=0;
}
void update(int L,int R,int l,int r,int rt)
{
if(L<=l and r<=R)
{
sum[rt]=(r-l+1)-sum[rt]; // [新增] 翻转当前区间
lazy[rt]^=1; // [新增] 标记翻转奇偶
return;
}
down(l,r,rt);
if(L<=mid) update(L,R,l,mid,lson);
if(R>mid) update(L,R,mid+1,r,rson);
sum[rt]=sum[lson]+sum[rson];
}
int query(int L,int R,int l,int r,int rt)
{
if(L<=l and r<=R) return sum[rt];
down(l,r,rt);
int sum=0;
if(L<=mid) sum+=query(L,R,l,mid,lson);
if(R>mid) sum+=query(L,R,mid+1,r,rson);
return sum;
}
signed main()
{
#ifndef ONLINE_JUDGE
freopen("c.in","r",stdin);
freopen("c.out","w",stdout);
#endif
int n,m;
cin>>n>>m;
while(m--)
{
int c,a,b;
cin>>c>>a>>b;
if(c==0) update(a,b,1,n,1);
else if(c==1) cout<<query(a,b,1,n,1)<<endl;
}
return 0;
}
未完工,待补充,2026.8.12

浙公网安备 33010602011771号