一 题意
给定一个数组A,定义Add(s, t, d)的操作为对A[s]...A[t]加d,定义Query(s, t)的操作为查询A[s] + ... + A[t]的值
二 算法
在一系列Query操作中,如果每次查询都把A[s]...A[t]加一次,那么要执行加法(t-s+1)次,算法复杂度为O(t-s+1),用线段树,
令线段<x, y>保存A[x]...A[y]的和,对于查询Query(s, t) , 只需在线段树中找到<s,t>对应的若干条线段<s, k>, <k+1, l>,...,<m, t>连接起来刚好组成线段<s, t>,我们把这几条线段称为<s,t>的匹配线段,把它们的和加起来即为所求,加法次数等于匹配
线段的条数,算法复杂度为O(log(t-s+1)),但Add操作就更复杂了,要把所有覆盖<s,t>的线段的和值都更新一遍,算法复杂
度变为O(log(t-s+1))
三 实现
每条线段加一个sum属性,用来保存<s,t>的部分和,再加一个d属性,保存A[s]...A[t]的共同增量,Add操作的时候
只需遍历到匹配线段,修改其d属性,不用再往下修改被匹配线段覆盖的孩子节点了。求A[s]+...+A[t]的时候,
把<s,t>的sum和那些覆盖<s,t>的线段的累计增量乘以(t-s+1)加起来即为所求:
A[s]+...+A[t] = <s,t>'s sum + 累计增量*(t-s+1)
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define DEBUG
#ifdef DEBUG
#define debug(...) printf( __VA_ARGS__)
#else
#define debug(...)
#endif
#define N 100001
struct segment_tree_node {
int s; /* 线段起始点 */
int t; /* 线段终点 */
long long sum; /* A[s]+...+A[t]的部分和 */
long long d; /* 公共增量 */
};
/* 虽然线段树不一定是完全二叉树,但依然可以用数组表示,只不过浪费一些空间 */
struct segment_tree_node segment_tree[3*N];
int A[N];
int n;
long long sum;
long long sum_d; /* 累计增量,深度遍历求和的时候用到*/
void make_segment_tree(int r, int s, int t)
{
int m, left;
struct segment_tree_node *node;
m = (s+t)>>1;
node = segment_tree + r;
node->s = s;
node->t = t;
node->d = 0;
left = r<<1; /* 左孩子 */
if (s == t) {
node->sum = A[s];
return;
}
make_segment_tree(left, s, m);
make_segment_tree(left+1, m+1, t);
node->sum = segment_tree[left].sum + segment_tree[left+1].sum;
}
/* 查找A[s]+...+A[t]的和,注意线段的sum只是部分和,
* 还要加上线段的增量, 如果线段树中不存在线段<s,t>,
* 则把<s,t>分割为多条线段,比如分割为<s,k>, <k+1,l>, <l+1,t>,
* 三条子线段,这三条线段都可以在线段树中找到匹配线段, 求出每条
* 匹配线段的和,加起来就是线段<s,t>的和
*/
void get_sum(int r, int s, int t)
{
int left, m;
struct segment_tree_node *node;
node = segment_tree + r;
m = (node->s+node->t)>>1;
left = r<<1;
/* 一条匹配线段找到,计算它的和,然后加给原始线段的和 */
if (node->s == s && node->t == t) {
sum += (node->sum + (t-s+1)*sum_d);
return;
}
/* 若不是匹配线段,更新增量,继续往下找 */
sum_d += node->d;
if (t <= m) {
get_sum(left, s, t);
}
else if (s > m) {
get_sum(left+1, s, t);
}
else {
get_sum(left, s, m);
get_sum(left+1, m+1, t);
}
sum_d -= node->d;
}
/* 对A[s]...A[t]之间的每一个元素加d, 体现在线段树中,
* 所有把匹配线段覆盖掉了的线段,都要更新sum值,而匹配
* 线段本身要更新d值
*/
void add(int r, int s, int t, long long int d)
{
int m, left;
struct segment_tree_node *node;
node = segment_tree + r;
m = (node->s+node->t)>>1;
left = r<<1;
node->sum += (t-s+1)*d; /* 覆盖匹配线段的那些线段,
都要更新sum值,匹配线段本身也包括在内 */
if (node->s == s && node->t == t) {
node->d += d; /* 只有匹配线段才更新增量值 */
return;
}
if (t <= m) {
add(left, s, t, d);
}
else if (s > m) {
add(left+1, s, t, d);
}
else {
add(left, s , m, d);
add(left+1, m+1, t, d);
}
}
int main()
{
int q, i, s, t, value;
char action;
scanf("%d %d", &n, &q);
for (i = 1; i <= n; i++) {
scanf("%d", A+i);
}
make_segment_tree(1, 1, n);
while (q--) {
getchar();
scanf("%c %d %d", &action, &s, &t);
if (action == 'Q') {
sum = 0;
sum_d = 0;
get_sum(1, s, t);
printf("%lld\n", sum);
}
else {
scanf("%d", &value);
add(1, s, t, value);
}
}
return 0;
}
本文介绍了一种高效的数据结构——线段树,用于快速处理数组区间上的加法和查询操作。通过递归构建线段树并利用增量更新,实现O(log n)的时间复杂度。

被折叠的 条评论
为什么被折叠?



