题目链接

区间增量与单点求值

题目描述

给定一个长度为 的数组,需要支持两种操作:

  1. 区间增量:将区间 内的所有元素加上一个值
  2. 单点求值:查询下标为 的元素的值。

解题思路

这是一个经典的数据结构问题,要求我们实现“区间更新”和“单点查询”。解决此类问题的标准方法之一是使用 差分数组 配合 树状数组 (Fenwick Tree)

  1. 差分数组

    • 首先,我们引入 差分 的思想。对于原数组 ,我们构建一个差分数组 ,使得: (对于 )

    • 从这个定义可以看出,原数组 的任意一项 都可以表示为差分数组 的一个前缀和:

    • 这样, 的单点查询就转化为了对 的区间和查询

  2. 转化区间更新

    • 接下来,我们分析“区间增量”操作对差分数组 的影响。将区间 的所有元素加上
      • 对于 ,其值变为 。这使得 。即 增加了
      • 对于任意 都增加了 ,所以它们的差 保持不变。
      • 对于 ,其值变为 。即 减少了
    • 综上, 的区间 增量操作,转化为了对 的两次单点更新 加上 减去
  3. 树状数组

    • 经过转化,原问题变成了对一个新数组(差分数组 )进行“单点更新”和“前缀和查询”。
    • 这正是树状数组最擅长解决的问题。我们可以用一个树状数组来维护差分数组 ,使得单点更新和前缀和查询的复杂度都为

综上,我们通过差分思想将问题巧妙地转化为树状数组的经典应用,从而高效地解决了问题。

代码

#include <iostream>
#include <vector>

using namespace std;
using ll = long long;

vector<ll> bit;
int n;

void update(int i, ll x) {
    for (; i <= n + 1; i += i & -i) {
        bit[i] += x;
    }
}

ll query(int i) {
    ll sum = 0;
    for (; i > 0; i -= i & -i) {
        sum += bit[i];
    }
    return sum;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int q;
    cin >> n >> q;
    bit.assign(n + 2, 0); 
    
    ll prev = 0;
    for (int i = 1; i <= n; ++i) {
        ll current;
        cin >> current;
        update(i, current - prev);
        prev = current;
    }

    while (q--) {
        int op;
        cin >> op;
        if (op == 1) {
            int l, r;
            ll x;
            cin >> l >> r >> x;
            update(l, x);
            update(r + 1, -x);
        } else {
            int p;
            cin >> p;
            cout << query(p) << "\n";
        }
    }
    return 0;
}
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.StreamTokenizer;

public class Main {
    static long[] bit;
    static int n;

    static void update(int i, long x) {
        for (; i <= n + 1; i += i & -i) {
            bit[i] += x;
        }
    }

    static long query(int i) {
        long sum = 0;
        for (; i > 0; i -= i & -i) {
            sum += bit[i];
        }
        return sum;
    }

    public static void main(String[] args) throws Exception {
        StreamTokenizer in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
        PrintWriter out = new PrintWriter(System.out);
        in.nextToken();
        n = (int) in.nval;
        in.nextToken();
        int q = (int) in.nval;
        bit = new long[n + 2];

        long prev = 0;
        for (int i = 1; i <= n; ++i) {
            in.nextToken();
            long current = (long) in.nval;
            update(i, current - prev);
            prev = current;
        }

        for (int k = 0; k < q; ++k) {
            in.nextToken();
            int op = (int) in.nval;
            if (op == 1) {
                in.nextToken();
                int l = (int) in.nval;
                in.nextToken();
                int r = (int) in.nval;
                in.nextToken();
                long x = (long) in.nval;
                update(l, x);
                update(r + 1, -x);
            } else {
                in.nextToken();
                int p = (int) in.nval;
                out.println(query(p));
            }
        }
        out.flush();
    }
}
n, q = 0, 0
bit = []

def update(i, x):
    """单点更新"""
    while i <= n + 1:
        bit[i] += x
        i += i & -i

def query(i):
    """前缀和查询"""
    s = 0
    while i > 0:
        s += bit[i]
        i -= i & -i
    return s

def main():
    global n, q, bit
    line1 = list(map(int, input().split()))
    n, q = line1[0], line1[1]

    bit = [0] * (n + 2)
    
    a = list(map(int, input().split()))
    
    prev = 0
    for i in range(n):
        update(i + 1, a[i] - prev)
        prev = a[i]

    for _ in range(q):
        line = list(map(int, input().split()))
        op = line[0]
        if op == 1:
            l, r, x = line[1], line[2], line[3]
            update(l, x)
            update(r + 1, -x)
        else:
            p = line[1]
            print(query(p))

if __name__ == "__main__":
    main()

算法及复杂度

  • 算法:差分数组 + 树状数组 (Fenwick Tree)
  • 时间复杂度:,其中初始化树状数组需要 ,每次操作需要
  • 空间复杂度:,用于存储树状数组。