题解:AtCoder AT_awc0047_c Piggy Bank Management

【题目来源】

AtCoder:C - Piggy Bank Management

【题目描述】

Takahashi manages \(N\) piggy banks. Each piggy bank is numbered from \(1\) to \(N\).
高桥管理着 \(N\) 个存钱罐。每个存钱罐编号从 \(1\)\(N\)

Each piggy bank \(i\) (\(1 \leq i \leq N\)) initially contains \(A_i\) yen. From now on, \(Q\) operations will be performed in order. Each operation is one of the following two types:
每个存钱罐 \(i\)\(1 \leq i \leq N\))初始时有 \(A_i\) 日元。接下来将按顺序执行 \(Q\) 次操作。每次操作是以下两种类型之一:

  • Type 1 (deposit): Positive integers \(L, R, X\) are given. Deposit \(X\) yen into each of the piggy banks \(L, L+1, \ldots, R\). That is, the amount in each of these piggy banks increases by \(X\) yen.
    类型 1(存入):给出正整数 \(L, R, X\)。向存钱罐 \(L, L+1, \ldots, R\) 中的每一个存入 \(X\) 日元。也就是说,这些存钱罐中的金额各增加 \(X\) 日元。
  • Type 2 (check): An integer \(P\) is given. Check the current amount in piggy bank \(P\).
    类型 2(查询):给出一个整数 \(P\)。查询存钱罐 \(P\) 的当前金额。

For each Type 2 operation, determine the amount of money currently in piggy bank \(P\).
对于每个类型 2 的操作,确定存钱罐 \(P\) 当前的金额。

【输入】

Input is given from standard input in the following format.

\(N\) \(Q\)
\(A_1\) \(A_2\) \(\ldots\) \(A_N\)
\(query_1\)
\(query_2\)
\(\vdots\)
\(query_Q\)

  • The first line contains the number of piggy banks \(N\) and the number of operations \(Q\), separated by a space.
  • The second line contains the initial amounts \(A_1, A_2, \ldots, A_N\) in each piggy bank, separated by spaces.
  • The following \(Q\) lines each contain the information for one operation \(query_1, query_2, \ldots, query_Q\). The leading integer of each \(query_i\) indicates the type of operation, and the format is one of the following:
  • Type 1: 1 \(L\) \(R\) \(X\) (deposit \(X\) yen into each piggy bank from \(L\) to \(R\))
  • Type 2: 2 \(P\) (check the amount in piggy bank \(P\))

【输出】

For each Type 2 operation, output the amount of money currently in piggy bank \(P\) on a single line.

【输入样例】

5 4
10 20 30 40 50
2 3
1 2 4 5
2 3
2 1

【输出样例】

30
35
10

【核心思想】

  1. 问题分析:给定 \(N\) 个存钱罐,初始金额为 \(A_i\)。进行 \(Q\) 次操作:操作 \(1\) 将区间 \([L, R]\) 内每个存钱罐的金额增加 \(X\);操作 \(2\) 查询存钱罐 \(P\) 的当前金额。这是一个树状数组维护差分数组问题,关键在于利用差分思想将区间修改转化为单点修改。

  2. 算法选择

    • 差分数组(Difference Array):树状数组存储差分值而非原数组值,实现区间修改的 \(O(\log N)\) 效率
    • 前缀和还原:单点查询通过求前缀和还原为实际值
    • 树状数组(Fenwick Tree / BIT):支持 \(O(\log N)\) 的单点修改和前缀和查询
  3. 关键步骤

    • 初始化
      • 读取 \(N\)(存钱罐数量)、\(Q\)(操作次数)
      • 读取初始金额 \(A[1..N]\)
      • 初始化树状数组:对每个位置 \(i\) 执行 add(i, A[i] - A[i-1]),存储差分值
    • 处理操作(共 \(Q\) 次):
      • 操作 \(1\)(区间修改):给定 \(L, R, X\)
        • add(L, X):在左边界加上 \(X\)
        • add(R+1, -X):在右边界后一个位置减去 \(X\)(恢复后续位置)
      • 操作 \(2\)(单点查询):给定 \(P\)
        • ans = query(P):查询前缀和,即为位置 \(P\) 的实际金额
        • 输出 ans
  4. 时间/空间复杂度

    • 时间复杂度:\(O((N + Q) \log N)\),初始化 \(O(N \log N)\),每次操作 \(O(\log N)\)
    • 空间复杂度:\(O(N)\),树状数组和原数组
  5. 树状数组维护差分的核心思想

    • 差分思想转化区间修改:差分数组 d[i] = a[i] - a[i-1],原数组可通过前缀和还原。区间 \([L, R]\)\(X\) 等价于 d[L] += Xd[R+1] -= X,将区间修改转化为两次单点修改
    • 前缀和还原单点值query(P) 计算差分数组的前缀和 \(\sum_{i=1}^{P} d[i]\),正好等于原数组位置 \(P\) 的值
    • 树状数组高效维护:树状数组支持 \(O(\log N)\) 的单点修改和前缀和查询,完美匹配差分思想的需求
    • 边界处理技巧:在 \(R+1\) 处减去 \(X\),确保只有区间 \([L, R]\) 内的位置受到影响,区间外的位置值不变
    • 适用于频繁区间修改、单点查询的场景

【算法标签】

树状数组

【代码详解】

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 200005;
int n, q;          // n: 数组长度, q: 查询次数
int a[N], tr[N];   // a: 原始数组, tr: 树状数组

// 计算x的二进制表示中最低位的1所代表的值
int lowbit(int x)  // 提取x的低位2次幂数
{
    return x & -x;  // 位运算技巧:x & (-x)
}

// 树状数组的更新操作:在位置x加上值c
void add(int x, int c)  // 向后修
{
    for (int i = x; i <= n; i += lowbit(i))
    {
        tr[i] += c;  // 更新所有包含位置x的区间
    }
}

// 树状数组的查询操作:查询前缀和[1,x]
int query(int x)  // 向前查
{
    int res = 0;
    for (int i = x; i; i -= lowbit(i))
    {
        res += tr[i];  // 累加所有相关区间
    }
    return res;
}

signed main()
{
    cin >> n >> q;  // 读取数组长度和查询次数

    // 读取原始数组
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
    }

    // 初始化树状数组
    // 将原始数组转换为差分数组存储到树状数组中
    for (int i = 1; i <= n; i++)
    {
        add(i, a[i] - a[i - 1]);  // 存储差分值
    }

    // 处理q个查询
    while (q--)
    {
        int op;
        cin >> op;  // 操作类型

        if (op == 1)  // 区间修改操作
        {
            int l, r, x;
            cin >> l >> r >> x;  // 区间[l,r]增加x

            // 树状数组实现区间修改的技巧:
            add(l, x);       // 在l处加上x
            add(r + 1, -x);  // 在r+1处减去x
        }
        else  // 单点查询操作
        {
            int p;
            cin >> p;  // 查询位置p的值

            // 输出位置p的值
            // query(p)返回的是前缀和,也就是位置p的实际值
            cout << query(p) << endl;
        }
    }
    return 0;
}

【运行结果】

5 4
10 20 30 40 50
2 3
30
1 2 4 5
2 3
35
2 1
10
posted @ 2026-06-15 09:49  团爸讲算法  阅读(10)  评论(0)    收藏  举报