F. Sum of Progression
F. Sum of Progression
You are given an array of numbers. There are also queries of the form .
For each query , find the sum of elements . In other words, for each query, it is necessary to find the sum of elements of the array with indices starting from the -th, taking steps of size , multiplying it by the serial number of the element in the resulting sequence.
Input
Each test consists of several testcases. The first line contains one integer () — the number of testcases. Next lines contain descriptions of testcases.
The first line of each testcase contains two numbers () — the number of elements in the array and the number of queries.
The second line contains integers () — elements of the array .
The next lines each contain three integers , , and (, ).
It is guaranteed that the sum of over all testcases does not exceed , and that the sum of over all testcases does not exceed .
Output
For each testcase, print numbers in a separate line — the desired sums, separated with space.
Example
input
5
3 3
1 1 2
1 2 2
2 2 1
1 1 2
3 1
-100000000 -100000000 -100000000
1 1 3
5 3
1 2 3 4 5
1 2 3
2 3 2
1 1 5
3 1
100000000 100000000 100000000
1 1 3
7 7
34 87 5 42 -44 66 -32
2 2 2
4 3 1
1 3 2
6 2 1
5 2 2
2 5 2
6 1 2
output
5 1 3
-600000000
22 12 55
600000000
171 42 118 66 -108 23 2
解题思路
对于询问 ,对应序列的下标的公差为 ,首项为 ,因此 是序列中的第 个元素, 是序列中第 个元素。那么答案 ,就可以通过前缀和得到:
由于每个询问保证 ,粗略看作是 ,对于这种形式的表达式可以考虑根号分治。由于每次询问暴力统计答案的时间复杂度为 ,因此如果在 时才暴力统计那么时间复杂度就变成 ,总的询问的时间复杂度就是 。
对于 的情况,我们就可以先预处理出所有下标公差不超过 的序列的前缀和,对应的时间复杂度为 ,此时就可以通过上面提到的公式以 的复杂度算出答案。
而预处理的话我们需要计算两种前缀和,一个是带权的前缀和,用 来表示下标公差为 的序列的前 个带权元素的和,转移方程为 。另外一个是普通前缀和,用 来表示下标公差为 的序列的前 个元素的和,转移方程为 。
AC 代码如下,时间复杂度为 :
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1e5 + 10, M = 310;
int a[N];
LL s1[M][N], s2[M][N];
void solve() {
int n, m;
scanf("%d %d", &n, &m);
for (int i = 1; i <= n; i++) {
scanf("%d", a + i);
}
for (int i = 1; i <= 300; i++) {
for (int j = 1; j <= n; j++) {
s1[i][j] = (j + i - 1ll) / i * a[j];
s2[i][j] = a[j];
if (j - i > 0) {
s1[i][j] += s1[i][j - i];
s2[i][j] += s2[i][j - i];
}
}
}
while (m--) {
int s, d, k;
scanf("%d %d %d", &s, &d, &k);
if (d <= 300) {
int l = max(0, s - d), r = s + d * (k - 1);
printf("%lld ", s1[d][r] - s1[d][l] - (l + d - 1) / d * (s2[d][r] - s2[d][l]));
}
else {
LL ret = 0;
for (int i = 0; i < k; i++) {
ret += (i + 1ll) * a[s + i * d];
}
printf("%lld ", ret);
}
}
printf("\n");
}
int main() {
int t;
scanf("%d", &t);
while (t--) {
solve();
}
return 0;
}
再给出我一开始的做法,也是想着预处理所有的前缀和,对于每个下标公差为 的序列有 个不同的首项,因此总的时间复杂度就是 。但最多只有 个询问,意味着只需计算出其中 个前缀和即可。
因此我的做法是对于每个询问 ,记录二元组 ,如果这个二元组之前没有出现过则求出下标公差为 首项为 序列的前缀和,并把结果保存下来用编号标记,如果之后询问的二元组还是这个就可以通过 算出结果。
考虑时间复杂度和空间复杂度,最坏的情况下每次询问的二元组 都不一样,并且是计算量最大的前 个序列,即 。因此时间复杂度和空间复杂度都是 。
当 和 都取题目中给定的最大值时,其中所用内存大概是 ,题目给出的限制是 ,然而一直 MLE。
所以优化的地方是对询问进行离线处理,把询问按照 进行分组,对于公差和首项都相同的询问统一处理,结束后把空间释放,这样空间复杂度就可以降到 了。
AC 代码如下,时间复杂度为 :
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 2e5 + 10;
int a[N];
LL s1[N], s2[N];
array<int, 3> q[N];
int p[N];
LL ans[N];
LL get(int x, int y) { // 把二元组映射成一个整数
return ((x - 1) % y + 1) * 100001ll + y;
}
void solve() {
int n, m;
scanf("%d %d", &n, &m);
for (int i = 1; i <= n; i++) {
scanf("%d", a + i);
}
for (int i = 0; i < m; i++) {
scanf("%d %d %d", &q[i][0], &q[i][1], &q[i][2]);
p[i] = i;
}
sort(p, p + m, [&](int i, int j) {
return get(q[i][0], q[i][1]) < get(q[j][0], q[j][1]);
});
for (int i = 0; i < m; i++) {
int s = q[p[i]][0], d = q[p[i]][1], k = q[p[i]][2];
for (int i = (s - 1) % d + 1, j = 1; i <= n; i += d, j++) { // 先求出前缀和
s1[j] = s1[j - 1] + 1ll * a[i] * j;
s2[j] = s2[j - 1] + a[i];
}
int j = i;
while (j < m && get(q[p[i]][0], q[p[i]][1]) == get(q[p[j]][0], q[p[j]][1])) { // 同一个序列统一处理
s = q[p[j]][0], d = q[p[j]][1], k = q[p[j]][2];
int l = (s + d - 1) / d - 1, r = (s + d * (k - 1) + d - 1) / d;
ans[p[j++]] = s1[r] - s1[l] - (s2[r] - s2[l]) * l;
}
i = j - 1;
}
for (int i = 0; i < m; i++) {
printf("%lld ", ans[i]);
}
printf("\n");
}
int main() {
int t;
scanf("%d", &t);
while (t--) {
solve();
}
return 0;
}
参考资料
Editorial for Codeforces Round 920 (Div. 3):https://codeforces.com/blog/entry/124757
Codeforces Round 920 (Div. 3) F Sum of Progression 根号分治+前缀和:https://zhuanlan.zhihu.com/p/678014982
本文来自博客园,作者:onlyblues,转载请注明原文链接:https://www.cnblogs.com/onlyblues/p/17968882
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
2023-01-16 最大价值