题解: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
【核心思想】
-
问题分析:给定 \(N\) 个存钱罐,初始金额为 \(A_i\)。进行 \(Q\) 次操作:操作 \(1\) 将区间 \([L, R]\) 内每个存钱罐的金额增加 \(X\);操作 \(2\) 查询存钱罐 \(P\) 的当前金额。这是一个树状数组维护差分数组问题,关键在于利用差分思想将区间修改转化为单点修改。
-
算法选择:
- 差分数组(Difference Array):树状数组存储差分值而非原数组值,实现区间修改的 \(O(\log N)\) 效率
- 前缀和还原:单点查询通过求前缀和还原为实际值
- 树状数组(Fenwick Tree / BIT):支持 \(O(\log N)\) 的单点修改和前缀和查询
-
关键步骤:
- 初始化:
- 读取 \(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
- 操作 \(1\)(区间修改):给定 \(L, R, X\)
- 初始化:
-
时间/空间复杂度:
- 时间复杂度:\(O((N + Q) \log N)\),初始化 \(O(N \log N)\),每次操作 \(O(\log N)\)
- 空间复杂度:\(O(N)\),树状数组和原数组
-
树状数组维护差分的核心思想:
- 差分思想转化区间修改:差分数组
d[i] = a[i] - a[i-1],原数组可通过前缀和还原。区间 \([L, R]\) 加 \(X\) 等价于d[L] += X和d[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
浙公网安备 33010602011771号