题解:AcWing 797 差分

【题目来源】

AcWing:797 差分 - AcWing题库

【题目描述】

输入一个长度为 \(n\) 的整数序列。

接下来输入 \(m\) 个操作,每个操作包含三个整数 \(l,r,c\),表示将序列中 \([l,r]\) 之间的每个数加上 \(c\)

请你输出进行完所有操作后的序列。

【输入】

第一行包含两个整数 \(n\)\(m\)

第二行包含 \(n\) 个整数,表示整数序列。

接下来 \(m\) 行,每行包含三个整数 \(l,r,c\),表示一个操作。

【输出】

共一行,包含 \(n\) 个整数,表示最终序列。

【输入样例】

6 3
1 2 2 1 2 1
1 3 1
3 5 1
1 6 1

【输出样例】

3 4 5 3 4 2

【核心思想】

  1. 问题分析:给定长度为 \(n\) 的序列 \(a[1..n]\),进行 \(m\) 次区间加操作,每次将 \([l, r]\) 内每个元素加上 \(c\)。若对每个操作暴力遍历区间修改,时间复杂度为 \(O(m \cdot n)\)。关键观察是:差分数组可以将区间加法转化为 \(O(1)\) 的单点更新,最后通过一次前缀和还原得到最终结果。

  2. 算法选择

    • 差分数组(Difference Array):定义 \(b[i] = a[i] - a[i-1]\)\(a[0] = 0\)),则区间 \([l, r]\)\(c\) 等价于 \(b[l] += c\)\(b[r+1] -= c\)
    • 前缀和还原:所有操作完成后,对差分数组求前缀和即可得到最终数组
  3. 关键步骤

    • 初始化差分数组
      • 方式一:\(b[i] = a[i] - a[i-1]\)(直接计算)
      • 方式二:对每个位置 \(i\) 执行 insert(i, i, a[i])(逐点插入)
    • 区间更新操作 insert(l, r, c)
      • \(b[l] \leftarrow b[l] + c\):从位置 \(l\) 开始,后续所有前缀和都增加 \(c\)
      • \(b[r+1] \leftarrow b[r+1] - c\):从位置 \(r+1\) 开始,抵消增加的影响,确保只有 \([l, r]\) 区间内的元素实际增加 \(c\)
    • 前缀和还原
      • \(i\)\(1\)\(n\)\(b[i] \leftarrow b[i] + b[i-1]\)
      • 此时 \(b[i]\) 即为最终数组的第 \(i\) 个元素
  4. 时间/空间复杂度

    • 时间复杂度:\(O(n + m)\),初始化差分数组 \(O(n)\)\(m\) 次操作每次 \(O(1)\),前缀和还原 \(O(n)\)
    • 空间复杂度:\(O(n)\),存储原数组和差分数组
  5. 差分数组的核心思想

    • 区间操作降维:差分数组将"区间 \([l, r]\)\(c\)"这一需要修改 \(r-l+1\) 个元素的操作,转化为两个单点更新 \(b[l] += c\)\(b[r+1] -= c\),实现 \(O(1)\) 的区间标记
    • 前缀和还原:差分是前缀和的逆运算。对差分数组求前缀和,恰好将 \(b[l]\) 处的 \(+c\) 传播到 \(l\) 及之后的所有位置,而 \(b[r+1]\) 处的 \(-c\)\(r+1\) 开始抵消,最终效果仅为 \([l, r]\) 区间增加 \(c\)
    • 离线处理优势:先记录所有操作的差分标记,最后统一通过前缀和还原,避免了重复遍历数组,特别适合大量区间更新后统一查询的场景
    • 边界处理技巧:在 \(r+1\) 处减 \(c\) 而非 \(r\) 处,确保只有 \([l, r]\) 内的位置受到影响,\(r = n\)\(r+1 = n+1\) 需数组多开一位
    • 适用于区间加减、区间更新、离线查询类问题,与前缀和互为逆运算

【解题思路】

【算法标签】

差分

【代码详解】

#include <bits/stdc++.h>
using namespace std;

const int N = 100010;  // 定义数组最大长度

int n, m;             // n: 数组长度, m: 操作次数
int a[N];             // 原始数组
int b[N];             // 差分数组

/**
 * 差分数组区间更新操作
 * @param l 区间左端点
 * @param r 区间右端点
 * @param c 要增加的值
 */
void insert(int l, int r, int c)
{
    b[l] += c;       // 左端点加c
    b[r + 1] -= c;   // 右端点+1位置减c
}

int main()
{
    // 输入数组长度和操作次数
    scanf("%d%d", &n, &m);

    // 输入原始数组
    for (int i = 1; i <= n; i++)
    {
        scanf("%d", &a[i]);
    }

    // 初始化差分数组(两种方式等价)
    // 方式1: 直接计算差分 b[i] = a[i] - a[i-1]
    // 方式2: 通过insert函数逐个元素插入
    for (int i = 1; i <= n; i++)
    {
        insert(i, i, a[i]);
    }

    // 处理每个区间更新操作
    while (m--)
    {
        int l, r, c;
        scanf("%d%d%d", &l, &r, &c);
        insert(l, r, c);  // 执行区间更新
    }

    // 计算前缀和,得到更新后的数组
    for (int i = 1; i <= n; i++)
    {
        b[i] += b[i - 1];
    }

    // 输出最终数组
    for (int i = 1; i <= n; i++)
    {
        printf("%d ", b[i]);
    }

    return 0;
}
#include <bits/stdc++.h>
using namespace std;

const int N = 100005;        // 定义最大数组长度

int n;                       // 数组元素个数
int m;                       // 操作次数
int s[N];                    // 原始数组/前缀和数组
int a[N];                    // 差分数组

int main()
{
    // 输入数组长度和操作次数
    cin >> n >> m;

    // 输入原始数组(作为前缀和数组)
    for (int i = 1; i <= n; i++)
    {
        cin >> s[i];
    }

    // 将前缀和数组转换为差分数组
    // 差分数组定义:a[i] = s[i] - s[i-1]
    for (int i = 1; i <= n; i++)
    {
        a[i] = s[i] - s[i - 1];
    }

    // 处理每个区间加减操作
    while (m--)
    {
        int L, R, c;         // 操作参数:区间[L,R]加上值c
        cin >> L >> R >> c;

        // 差分数组的核心操作:
        // 在L位置加c,会影响从L开始的所有前缀和
        a[L] += c;

        // 在R+1位置减c,抵消从R+1开始的影响
        // 这样只有区间[L,R]内的元素会受到影响
        a[R + 1] -= c;
    }

    // 通过差分数组重新计算前缀和数组
    // 恢复更新后的数组值
    for (int i = 1; i <= n; i++)
    {
        s[i] = s[i - 1] + a[i];
    }

    // 输出更新后的数组
    for (int i = 1; i <= n; i++)
    {
        cout << s[i] << " ";
    }

    return 0;
}

【运行结果】

6 3
1 2 2 1 2 1
1 3 1
3 5 1
1 6 1
3 4 5 3 4 2
posted @ 2026-07-02 22:46  团爸讲算法  阅读(12)  评论(0)    收藏  举报