题解:AtCoder AT_awc0032_e Multiple Bonus
【题目来源】
AtCoder:E - Multiple Bonus
【题目描述】
Takahashi is a department manager who manages \(N\) employees. Each employee is assigned an employee number from \(1\) to \(N\), and the initial evaluation points of employee \(i\) are \(S_i\).
高桥是一位部门经理,管理着 \(N\) 名员工。每位员工被分配了一个从 \(1\) 到 \(N\) 的员工编号,员工 \(i\) 的初始评分为 \(S_i\)。
Let \(T_i\) denote the current evaluation points of employee \(i\). Initially, \(T_i = S_i\) (\(1 \leq i \leq N\)).
设 \(T_i\) 表示员工 \(i\) 当前的评分。初始时,\(T_i = S_i\)(\(1 \leq i \leq N\))。
This company has a unique evaluation system. When a certain employee \(k\) (\(1 \leq k \leq N\)) achieves results as a team leader, bonus points corresponding to the achievement are added to the evaluation points of all employees whose employee numbers are multiples of \(k\) (\(k, 2k, 3k, \ldots\)) and are at most \(N\). Since \(k\) itself is a multiple of \(k\), it is included in the targets.
这家公司有一套独特的评分系统。当某位员工 \(k\)(\(1 \leq k \leq N\))作为团队领导取得成果时,与成就对应的奖励分数会被加到所有员工编号是 \(k\) 的倍数(\(k, 2k, 3k, \ldots\))且不超过 \(N\) 的员工的评分上。由于 \(k\) 本身是 \(k\) 的倍数,因此也包含在目标中。
Takahashi performs \(Q\) operations on this evaluation system. Each operation is one of the following two types:
高桥对该评分系统执行 \(Q\) 次操作。每次操作是以下两种类型之一:
- Operation \(1\): Given a positive integer \(k\) (\(1 \leq k \leq N\)) and a positive integer \(v\). Add \(v\) to the evaluation points \(T_j\) of all employees \(j\) whose employee numbers are multiples of \(k\) and are at most \(N\) (i.e., \(j = k, 2k, 3k, \ldots\) and \(j \leq N\)).
操作 \(1\):给定一个正整数 \(k\)(\(1 \leq k \leq N\))和一个正整数 \(v\)。将所有员工编号是 \(k\) 的倍数且不超过 \(N\) 的员工 \(j\)(即 \(j = k, 2k, 3k, \ldots\) 且 \(j \leq N\))的评分 \(T_j\) 增加 \(v\)。 - Operation \(2\): Given a positive integer \(x\) (\(1 \leq x \leq N\)). Output the sum of the current evaluation points from employee \(1\) to employee \(x\): \(\displaystyle\sum_{i=1}^{x} T_i\).
操作 \(2\):给定一个正整数 \(x\)(\(1 \leq x \leq N\))。输出从员工 \(1\) 到员工 \(x\) 的当前评分之和:\(\displaystyle\sum_{i=1}^{x} T_i\)。
For all operations of type \(2\), output the correct answer.
对于所有类型 \(2\) 的操作,输出正确答案。
【输入】
\(N\) \(Q\)
\(S_1\) \(S_2\) \(\ldots\) \(S_N\)
\(\mathrm{query}_1\)
\(\mathrm{query}_2\)
\(\vdots\)
\(\mathrm{query}_Q\)
- The first line contains the number of employees \(N\) and the number of operations \(Q\), separated by a space.
- The second line contains the initial evaluation points \(S_1, S_2, \ldots, S_N\) of each employee, separated by spaces.
- The following \(Q\) lines each contain one operation.
- For operation \(1\): given in the format
1 k v. - For operation \(2\): given in the format
2 x.
【输出】
Each time operation \(2\) is given, output the sum of evaluation points from employee \(1\) to employee \(x\) on one line.
【输入样例】
5 6
1 2 3 4 5
2 3
1 2 10
2 5
1 1 1
2 1
2 4
【输出样例】
6
35
2
34
【核心思想】
-
问题分析:给定 \(N\) 名员工,初始评分为 \(S_i\)。有两种操作:操作 \(1\) 将所有编号为 \(k\) 的倍数的员工评分增加 \(v\);操作 \(2\) 查询前 \(x\) 名员工的总评分。这是一个树状数组 + 分块优化问题,关键在于高效处理倍数位置更新和前缀和查询。
-
算法选择:
- 树状数组(Fenwick Tree / BIT):维护直接修改的贡献,支持 \(O(\log N)\) 单点修改和 \(O(\log N)\) 前缀和查询
- 分块优化(Block Decomposition):以 \(\sqrt{N}\) 为阈值,将小 \(k\)(\(k \leq \sqrt{N}\))的修改用标记数组记录,大 \(k\)(\(k > \sqrt{N}\))直接修改树状数组
- 倍数贡献计算:对于小 \(k\),查询时通过
tag[k] * (x / k)快速计算其对前 \(x\) 个位置的总贡献
-
关键步骤:
- 初始化:
- 读取 \(N\)(员工数量)、\(Q\)(操作次数)
- 读取初始评分 \(S[1..N]\),计算前缀和
sa[i] = sa[i-1] + S[i] - 计算分块阈值 \(B = \sqrt{N}\)
- 初始化树状数组
tr和分块标记数组tag
- 处理操作(共 \(Q\) 次):
- 操作 \(1\)(修改):给定 \(k\) 和 \(v\)
- 若 \(k \leq B\)(小 \(k\)):
tag[k] += v,用分块标记记录 - 若 \(k > B\)(大 \(k\)):遍历 \(i = k, 2k, 3k, \ldots \leq N\),对每个位置执行
add(i, v)修改树状数组
- 若 \(k \leq B\)(小 \(k\)):
- 操作 \(2\)(查询):给定 \(x\)
- 计算基础前缀和:
ans = sa[x] - 加上树状数组的贡献:
ans += query(x) - 加上分块标记的贡献:遍历 \(k = 1\) 到 \(B\),
ans += tag[k] * (x / k)(前 \(x\) 个位置中 \(k\) 的倍数有 \(\lfloor x/k \rfloor\) 个) - 输出
ans
- 计算基础前缀和:
- 操作 \(1\)(修改):给定 \(k\) 和 \(v\)
- 初始化:
-
时间/空间复杂度:
- 时间复杂度:\(O((N + Q) \sqrt{N})\),修改操作小 \(k\) 为 \(O(1)\),大 \(k\) 为 \(O(N/k) \leq O(\sqrt{N})\);查询操作为 \(O(\sqrt{N} + \log N)\)
- 空间复杂度:\(O(N)\),树状数组、前缀和数组、分块标记数组
-
树状数组 + 分块优化的核心思想:
- 阈值分治:以 \(\sqrt{N}\) 为界,将操作分为两类分别处理。小 \(k\) 的修改次数多但单次影响位置少,用标记数组延迟计算;大 \(k\) 的修改次数少但单次影响位置多,直接修改树状数组
- 倍数贡献快速计算:对于小 \(k\),前 \(x\) 个位置中 \(k\) 的倍数数量为 \(\lfloor x/k \rfloor\),乘以标记值即可得到总贡献,避免遍历所有位置
- 树状数组维护大 \(k\) 修改:大 \(k\) 每次修改的位置数不超过 \(\sqrt{N}\),直接单点修改树状数组,查询时累加
- 前缀和预处理:初始值的前缀和
sa预处理,避免重复计算 - 适用于倍数更新、区间查询、操作分类优化类问题
【解题思路】

【算法标签】
树状数组
【代码详解】
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 200005;
int n, q, s[N], sa[N], tr[N], tag[N]; // s: 原始数组, sa: 前缀和, tr: 树状数组, tag: 分块标记
int lowbit(int x) // 提出x的低位2次幂数
{
return x & -x;
}
void add(int x, int c) // 向后修:树状数组单点增加
{
for (int i = x; i <= n; i += lowbit(i))
{
tr[i] += c;
}
}
int query(int x) // 向前查:树状数组前缀和查询
{
int res = 0;
for (int i = x; i; i -= lowbit(i))
{
res += tr[i];
}
return res;
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n >> q; // 输入数组长度和操作次数
for (int i = 1; i <= n; i++)
{
cin >> s[i]; // 输入原始数组
sa[i] = sa[i - 1] + s[i]; // 计算前缀和
}
int B = sqrt(n); // 分块阈值
while (q--)
{
int op;
cin >> op; // 操作类型
if (op == 1) // 修改操作
{
int k, v;
cin >> k >> v; // 对k的倍数位置增加v
if (k <= B) // 小k,使用分块标记
{
tag[k] += v;
}
else // 大k,直接修改树状数组
{
for (int i = k; i <= n; i += k)
{
add(i, v);
}
}
}
else // 查询操作
{
int x;
cin >> x; // 查询前缀和
int ans = sa[x] + query(x); // 基础前缀和+树状数组修改
for (int k = 1; k <= B; k++) // 加上分块标记的贡献
{
ans += tag[k] * (x / k);
}
cout << ans << endl;
}
}
return 0;
}
【运行结果】
5 6
1 2 3 4 5
2 3
6
1 2 10
2 5
35
1 1 1
2 1
2
2 4
34
浙公网安备 33010602011771号