题目
https://codeforces.com/problemset/problem/2026/D
题意
第一行输入一个正整数 \(n(1 \leq n \leq 3e5)\),第二行输入 \(n\) 个整数 \(a_1, a_2, ..., a_i, ..., a_n(-10 \leq a_i \leq 10)\),第三行输入一个正整数 \(q(1 \leq q \leq 3e5)\),随后 \(q\) 行,每行输入两个整数 \(l_i, r_i(1 \leq l_i \leq r_i \leq \frac{n(n+1)}{2})\)。
对于数组 \(a\),我们会得到一个数组 \(b = [a_1, a_1 + a_2, ..., a_1 + a_2 + ... + a_n, a_2, a_2 + a_3, ..., a_2 + a_3 + ... + a_n, ..., a_n]\) 共 \(\frac{n(n + 1)}{2}\) 个元素,对于每个询问要求的是 \(\sum_{j = l_i}^{r_i}{b_j}\)。
题解
对于数组 \(a = [a_1, a_2, ..., a_i, ..., a_n]\),由于 \(1 \leq n \leq 3e5\),那么 \(b\) 元素个数会达到 \(1 \leq \frac{n(n+1)}{2} \leq 45000000000\) 的量级,因此不能直接求出 \(b\) 的每一个元素。不妨对数组 \(b\) 分为 \(n\) 组:
第一组
\(a_1, a_1 + a_2, ..., a1_ + a_2 + ... + a_n\)
第二组
\(a_2, a_2 + a_3, ..., a_2 + a_3 + ... + a_n\)
...
第 \(n\) 组
\(a_n\)
易知从第一组到第 \(n\) 组的元素个数分别是 \(n, n - 1, ..., 1\),若只是求出其中一组,时间复杂度为 \(O(n)\) 是可以接受的。
观察可以发现:第二组可以由第一组每个元素都删去一个 \(a_1\) 得到;第三组可以由第二组每个元素都删去一个 \(a_2\) 得到;以此类推。于是可以先计算出\(sum_1(a_1 + a_2 + ... + a_n)\),\(sum_2(a_2 + ... + a_n)\), ..., \(sum_n(a_n)\)。
假设我想计算 \(b\) 第 \(1 ~ x\) 项的和,那么需要计算出第 \(x\) 项位于第几个 \(sum\),计算方式可以使用二分法。不妨视为第 \(i\) 个,然后计算 \(sum_i\) 需要删掉的元素个数 \(cnt\)。此时问题就转化为如何计算出需要删掉的元素的和,或者是如何计算出最后一组不包括被删除元素的元素之和?
不妨假设 \(n = 4,x = 2\),那么此时 \(cnt = 2,sum_1 = a_1 + (a_1 + a_2) + (a_1 + a_2 + a_3) + (a_1 + a_2 + a_3 + a_4),\sum_{j=1}^{2}{b_j} = a_1 + (a_1 + a_2)\),可得 \(\sum_{j=1}^{2}{b_j} = sum_1 - (a_1 + a_2 + a_3) - (a_1 + a_2 + a_3 + a_4)\)。
删除的元素可以视为 \((a_1 + a_2 + a_3) + (a_1 + a_2 + a_3 + a_4) = 2 \times (a_1 + a_2) + a_3 + (a_3 + a_4) = cnt \times (a_1 + a_2) + a_3 + (a_3 + a_4)\)。观察分好组的数据,易知 \(a_3 + (a_3 + a_4)\) 刚好是倒数第 \(cnt\) 组。由归纳法,可以得出结论:
\(sum_i - sum_cnt - cnt \times 剩余的数最大值\) 就是最后一个 \(sum_i\) 剩余的值。至于前面的 \(sum_1~sum_i-1\) 直接使用前缀和维护即可。
参考代码
#include<bits/stdc++.h>
#define IOS ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
using namespace std;
using ll = long long;
constexpr int N = 3e5 + 7;
int n, q;
ll l, r;
ll a[N];
ll b[N];//第 i 组元素的总和
ll c[N];//维护第 1~i 组的总元素个数,分别是 n, n + (n - 1), n + (n - 1) + (n - 2), ...
ll apre[N];//a的前缀和
ll apst[N];//a的后缀和
ll bpre[N];//b的前缀和
ll calc(ll x) {//计算出从 b1~bx 的和
int loc = lower_bound(c + 1, c + 1 + n, x) - c;//计算出第 x 项所在组 loc
ll res = bpre[loc];
if (c[loc] > x) {
res = bpre[loc - 1];
int deleteNum = c[loc] - x;//需要删掉的数的数量
ll lastNum = apst[loc] - apst[n - deleteNum + 1];//删掉 deleteNum 个数后剩余最大元素
res += b[loc] - b[n - deleteNum + 1] - lastNum * deleteNum;//第 loc 组的和减去 倒数第 deleteNum 组的和并减去 deletNum 个剩余最大元素
}
return res;
}
int main() {
cin >> n;
for (int i = 1; i <= n; ++ i) {
cin >> a[i];
apre[i] += apre[i - 1] + a[i];//求数组 a 的前缀和
}
for (int i = n; i >= 1; -- i) {
apst[i] = apst[i + 1] + a[i];//求数组 b 的前缀和
b[i] = apre[i] + b[i + 1];//数组 a 的前缀和的前缀和,也就是第 i 组的和
}
c[1] = n;//第一组的元素个数
for (int i = 2; i <= n; ++ i) {
c[i] = c[i - 1] + (n - i + 1);//第一组到第 i 组的总元素个数
b[i] = b[i - 1] - a[i - 1] * (n - i + 2);//第 i 组的元素之和
}
for (int i = 1; i <= n; ++ i) bpre[i] = b[i] + bpre[i - 1];//数组 b 前缀和的前缀和,也就是第一组到第 i 组的总元素之和
cin >> q;
while (q --) {
cin >> l >> r;
cout << calc(r) - calc(l - 1) << '\n';
}
return 0;
}
标签:...,ll,loc,Segments,元素,Sums,codeforces,leq,sum
From: https://www.cnblogs.com/RomanLin/p/18574549