P3372 【模板】线段树 1 线段树/分块解法
线段树模板题解析
解题思路
这道题是一个典型的线段树应用问题,需要高效地处理区间更新(区间加值)和区间查询(区间求和)两种操作。线段树是一种二叉树结构,能够以O(logN)的时间复杂度完成这两种操作。
解题步骤:
-
构建线段树:将原始数组构建成线段树,每个节点存储对应区间的和
-
区间更新:使用懒标记(lazy tag)技术延迟更新,只在需要时才将标记下推,提高效率
-
区间查询:查询时同样需要处理懒标记,确保数据正确性
代码注释
#include<bits/stdc++.h> #define f(i,s,e) for(int i = s; i <= e; i++) // 定义循环宏,从s到e #define lc rt * 2 // 左子节点索引 #define rc rt * 2 + 1 // 右子节点索引 #define lson lc,l,mid // 左子树参数 #define rson rc,mid +1,r // 右子树参数 #define ll long long // 定义long long类型别名 using namespace std; const int N = 2e5+10, inf = 0x3f3f3f3f; // 线段树节点结构体 struct node{ ll sum, lazy; // sum存储区间和,lazy是懒标记 }; node t[N * 4]; // 线段树数组,大小是4倍原始数组 ll n, m, a[N]; // n-数组长度,m-操作次数,a-原始数组 // 更新父节点的sum值为左右子节点sum之和 void pushup(int rt) { t[rt].sum = t[lc].sum + t[rc].sum; } // 对节点rt进行区间[l,r]的延迟更新操作,增加z void down(int rt, int l, int r, ll z) { t[rt].sum += (r - l + 1) * z; // 更新区间和 t[rt].lazy += z; // 累加懒标记 } // 将当前节点的懒标记下推到子节点 void pushdown(int rt, int l, int r) { if(t[rt].lazy == 0) return; // 没有懒标记则直接返回 int mid = (l + r) >> 1; down(lson, t[rt].lazy); // 下推左子树 down(rson, t[rt].lazy); // 下推右子树 t[rt].lazy = 0; // 清空当前节点懒标记 } // 构建线段树 void build(int rt, int l, int r) { if(l == r) { // 到达叶子节点 t[rt].sum = a[l]; // 存储数组值 t[rt].lazy = 0; // 初始化懒标记 return; } int mid = (l + r) / 2; build(lson); // 构建左子树 build(rson); // 构建右子树 pushup(rt); // 更新当前节点 } // 区间[x,y]每个元素加z void add(int rt, int l, int r, int x, int y, ll z) { if(r < x || y < l) return; // 当前区间与目标区间无交集 if(x <= l && r <= y) { // 当前区间完全包含在目标区间内 t[rt].sum += (r - l + 1) * z; // 更新区间和 t[rt].lazy += z; // 设置懒标记 return; } int mid = (l + r) >> 1; pushdown(rt, l, r); // 下推懒标记 add(lson, x, y, z); // 更新左子树 add(rson, x, y, z); // 更新右子树 pushup(rt); // 更新当前节点 } // 查询区间[x,y]的和 ll query(int rt, int l, int r, int x, int y) { if(r < x || y < l) return 0; // 当前区间与查询区间无交集 if(x <= l && r <= y) { // 当前区间完全包含在查询区间内 return t[rt].sum; // 直接返回区间和 } int mid = (l + r) / 2; pushdown(rt, l, r); // 下推懒标记 return query(lson, x, y) + query(rson, x, y); // 返回左右子树查询结果之和 } int main() { scanf("%d%d", &n, &m); // 读取数组长度和操作次数 f(i,1,n) scanf("%lld", &a[i]); // 读取初始数组 build(1,1,n); // 构建线段树 while(m--) { ll op, x, y, z; scanf("%lld", &op); // 读取操作类型 if(op == 1) { // 区间加操作 scanf("%lld%lld%lld", &x, &y, &z); add(1,1,n,x,y,z); } else { // 区间查询操作 scanf("%lld%lld", &x, &y); printf("%lld\n", query(1,1,n,x,y)); } } return 0; }
解题思路
本题要求实现区间修改(区间加值)和区间查询(区间求和)两种操作。虽然题目名为"线段树模板",但这里采用了分块算法来实现,这是一种介于暴力与线段树之间的折中方案。
分块算法的核心思想是将数组分成若干块,对完整块进行整体操作,对不完整块进行暴力操作。这样可以在O(√n)的时间复杂度内完成每次操作,比纯暴力的O(n)更高效,又比线段树的实现更简单。
代码详细注释
#include<bits/stdc++.h> #define ll long long // 定义long long为ll,方便使用 using namespace std; const int N = 1e5 + 10; // 数组大小,比题目最大范围稍大 // 定义全局变量 ll a[N], // 原始数组 sum[N], // 每个块的和 lazy[N]; // 每个块的懒标记(记录未下放的加法操作) int n, m, // n是数组长度,m是操作次数 block; // 每个块的大小 // 区间修改函数:将区间[x,y]内每个数加上k void change(int x, int y, ll k) { int bl = x / block, // x所在块的编号 br = y / block; // y所在块的编号 // 情况1:x和y在同一块内 if (bl == br) { // 直接遍历区间内所有元素进行修改 for (int i = x; i <= y; i++) { a[i] += k; // 元素值增加k sum[bl] += k; // 所在块的和增加k } } // 情况2:x和y在不同块 else { // 处理x所在的不完整块(前半部分) for (int i = x; i < (bl + 1) * block; i++) { a[i] += k; sum[bl] += k; } // 处理中间的完整块 for (int i = bl + 1; i < br; i++) { lazy[i] += k; // 懒标记增加k(表示整个块都加了k) sum[i] += k * block; // 块和增加k*块大小 } // 处理y所在的不完整块(后半部分) for (int i = br * block; i <= y; i++) { a[i] += k; sum[br] += k; } } } // 区间查询函数:查询区间[x,y]内所有数的和 ll query(int x, int y) { ll res = 0; // 结果变量 int bl = x / block, // x所在块的编号 br = y / block; // y所在块的编号 // 情况1:x和y在同一块内 if (bl == br) { // 遍历区间内所有元素求和 for (int i = x; i <= y; i++) { res += a[i] + lazy[bl]; // 元素值加上所在块的懒标记 } } // 情况2:x和y在不同块 else { // 处理x所在的不完整块(前半部分) for (int i = x; i < (bl + 1) * block; i++) { res += a[i] + lazy[bl]; } // 处理中间的完整块 for (int i = bl + 1; i < br; i++) { res += sum[i]; // 直接加上整个块的和 } // 处理y所在的不完整块(后半部分) for (int i = br * block; i <= y; i++) { res += a[i] + lazy[br]; } } return res; } int main() { // 输入输出优化 ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); // 读取数据 cin >> n >> m; block = sqrt(n); // 计算块大小(取平方根) // 初始化数组和块信息 for (int i = 1; i <= n; i++) { cin >> a[i]; sum[i / block] += a[i]; // 将元素值加入对应块的和 } // 处理每个操作 while (m--) { int op, x, y; ll k; cin >> op; // 操作1:区间加值 if (op == 1) { cin >> x >> y >> k; change(x, y, k); } // 操作2:区间查询 else { cin >> x >> y; cout << query(x, y) << "\n"; } } return 0; }

浙公网安备 33010602011771号