U552155 J-C 序列分段
题目描述
给定长度为 n n n 的序列 a a a,要求将 a a a 分割成恰好 k k k 段,每一段内的所有数字都求和,得到一个长度为 k k k 的序列 b b b。
接着,最大化以下式子的和:
∑
i
=
1
k
i
×
b
i
。
\sum\limits_{i=1}^k i\times b_i。
i=1∑ki×bi。
即:
b
1
×
1
+
b
2
×
2
+
b
3
×
3
+
.
.
.
+
b
k
×
k
b_1\times1+b_2\times 2+b_3 \times3+...+b_k\times k
b1×1+b2×2+b3×3+...+bk×k。
更通俗的:
请最大化:“第一段的和乘 1 1 1,加上第二段的和乘 2 2 2,一直加到第 k k k 段的和乘 k k k ”。
现在,请你对每一个 k ( 1 ≤ k ≤ n ) k\ (1 \leq k \leq n) k (1≤k≤n),都求出并回答上述式子的最大值吧。
输入格式
本题有多组测试数据。
输入的第一行包含一个正整数 T T T,表示数据组数。
接下来包含 T T T 组数据,每组数据的格式如下:
第一行一个正整数 n n n,表示序列 a a a 的长度。
第二行 n n n 个整数 a 1 , a 2 , ⋯ , a n a_1, a_2, \cdots,a_n a1,a2,⋯,an,表示序列 a a a。
输出格式
对于每组测试数据:
在单独的一行输出由空格分隔的 n n n 个整数,其中第 i i i 个整数表示:把数组分为 i i i 段时,上述式子的最大值。
输入输出样例 #1
输入 #1
2
6
1 3 -4 5 -1 -2
1
100
输出 #1
2 4 5 3 1 -2
100
说明/提示
【样例 1 解释】
对于第一组测试数据,我们考虑 k = 3 k=3 k=3 的情况,可以把序列分为:
{ { 1 } , { 3 , − 4 } , { 5 , − 1 , − 2 } } \{\{1\},\{3, -4\}, \{5, -1, -2\} \} {{1},{3,−4},{5,−1,−2}}。
此时 b = { 1 , 3 + ( − 4 ) , ( 5 + ( − 1 ) + ( − 2 ) ) } = { 1 , − 1 , 2 } b=\{1, 3+(-4), (5+(-1)+(-2))\}=\{1,-1,2\} b={1,3+(−4),(5+(−1)+(−2))}={1,−1,2}。
而题目所求式子的值为: 1 × 1 + ( − 1 ) × 2 + 2 × 3 = 5 1\times1+(-1)\times 2+2 \times3=5 1×1+(−1)×2+2×3=5。
因此第一组测试数据中,第三个数字的值是 5 5 5。
(可以证明,不存在比 5 5 5 更优的答案。)
【数据范围】
令 N N N 表示 T T T 组数据中 n n n 的总和。
对于 30 % \rm 30\% 30% 的数据有: T = 1 , 1 ≤ N ≤ 15 T = 1, 1 \leq N \leq 15 T=1,1≤N≤15。
对于 60 % 60\% 60% 的数据有: 1 ≤ T ≤ 10 , 1 ≤ N ≤ 200 1 \leq T \leq 10, 1 \leq N \leq 200 1≤T≤10,1≤N≤200。
对于所有的测试数据有: 1 ≤ T ≤ 100 , 1 ≤ N ≤ 2 × 1 0 5 , − 1 0 6 ≤ a i ≤ 1 0 6 1 \leq T \leq 100, 1 \leq N \leq 2 \times 10^5\ ,-10^6 \leq a_i \leq 10^6 1≤T≤100,1≤N≤2×105 ,−106≤ai≤106。
思路:以下是针对这道题的解题思路:
核心思路
本题的核心目标是对于给定的序列 a a a,将其分割成 k k k 段( 1 ≤ k ≤ n 1\leq k\leq n 1≤k≤n),并使得 ∑ i = 1 k i × b i \sum_{i = 1}^{k}i\times b_i ∑i=1ki×bi 这个式子的值最大,其中 b i b_i bi 是第 i i i 段的数字之和。
步骤分析
1. 前缀和数组的计算
为了能够快速计算任意区间的和,我们需要先计算序列
a
a
a 的前缀和数组 prefixSum
。对于长度为
n
n
n 的序列
a
a
a,前缀和数组 prefixSum
的长度为
n
+
1
n + 1
n+1,其中 prefixSum[i]
表示序列
a
a
a 中前
i
i
i 个元素的和。
在代码中,这部分的实现如下:
vector<long long> prefixSum(n + 1, 0);
for (int i = 1; i <= n; ++i) {
prefixSum[i] = prefixSum[i - 1] + a[i - 1];
}
2. 分析分割的影响
当我们把序列分割成 k k k 段时,要使得 ∑ i = 1 k i × b i \sum_{i = 1}^{k}i\times b_i ∑i=1ki×bi 最大,我们需要让较大的区间和尽量处于后面的段。因为后面的段在求和时会乘以更大的系数 i i i。
考虑分割点,假设在位置
i
i
i 进行分割,那么后半部分的和为 prefixSum[n] - prefixSum[i]
。我们可以发现,对于不同的
k
k
k,我们总是希望选取那些后半部分和较大的分割点。
3. 提前计算并排序分割点的差值
为了避免在每次计算 k k k 段分割时都重复计算和排序,我们可以提前计算所有可能的分割点的差值,并对这些差值进行排序。
代码实现如下:
vector<long long> diffs;
for (int i = 1; i < n; ++i) {
diffs.push_back(prefixSum[n] - prefixSum[i]);
}
sort(diffs.begin(), diffs.end(), greater<long long>());
4. 计算不同 k k k 段分割的最大值
- 当
k
=
1
k = 1
k=1 时,整个序列作为一段,此时
∑
i
=
1
1
i
×
b
i
\sum_{i = 1}^{1}i\times b_i
∑i=11i×bi 就是整个序列的和,即
prefixSum[n]
。 - 当 k > 1 k > 1 k>1 时,我们从排序好的差值中依次选取较大的差值,累加到之前的和中。
代码实现如下:
vector<long long> ans(n);
long long sum = prefixSum[n];
ans[0] = sum;
for (int k = 1; k < n; ++k) {
sum += diffs[k - 1];
ans[k] = sum;
}
整体流程
- 读取测试数据的组数 T T T。
- 对于每组测试数据:
- 读取序列的长度 n n n 和序列 a a a。
- 计算前缀和数组
prefixSum
。 - 提前计算并排序所有可能的分割点的差值。
- 计算不同 k k k 段分割的最大值。
- 输出结果。
复杂度分析
- 时间复杂度:计算前缀和的时间复杂度为 O ( n ) O(n) O(n),计算并排序差值的时间复杂度为 O ( n log n ) O(n\log n) O(nlogn),计算不同 k k k 段分割的最大值的时间复杂度为 O ( n ) O(n) O(n),因此对于每组数据的时间复杂度为 O ( n log n ) O(n\log n) O(nlogn)。由于有 T T T 组数据,且 ∑ i = 1 T n i ≤ 2 × 1 0 5 \sum_{i = 1}^{T}n_i\leq 2\times 10^5 ∑i=1Tni≤2×105,所以总的时间复杂度为 O ( N log N ) O(N\log N) O(NlogN),其中 N N N 是所有数据的 n n n 的总和。
- 空间复杂度:主要使用了前缀和数组、差值数组和结果数组,空间复杂度为 O ( n ) O(n) O(n)。
ac代码:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
// 解决一组测试数据的函数
vector<long long> solve(const vector<int>& a) {
int n = a.size();
// 计算前缀和数组
vector<long long> prefixSum(n + 1, 0);
for (int i = 1; i <= n; ++i) {
prefixSum[i] = prefixSum[i - 1] + a[i - 1];
}
// 提前计算所有可能的分割点的差值并排序
vector<long long> diffs;
for (int i = 1; i < n; ++i) {
diffs.push_back(prefixSum[n] - prefixSum[i]);
}
sort(diffs.begin(), diffs.end(), greater<long long>());
vector<long long> ans(n);
long long sum = prefixSum[n];
ans[0] = sum;
for (int k = 1; k < n; ++k) {
sum += diffs[k - 1];
ans[k] = sum;
}
return ans;
}
int main() {
int T;
cin >> T;
while (T--) {
int n;
cin >> n;
vector<int> a(n);
for (int i = 0; i < n; ++i) {
cin >> a[i];
}
vector<long long> result = solve(a);
for (int i = 0; i < n; ++i) {
if (i > 0) cout << " ";
cout << result[i];
}
cout << endl;
}
return 0;
}
超时代码:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
// 解决一组测试数据的函数
vector<long long> solve(const vector<int>& a) {
int n = a.size();
// 计算前缀和数组
vector<long long> prefixSum(n + 1, 0);
for (int i = 1; i <= n; ++i) {
prefixSum[i] = prefixSum[i - 1] + a[i - 1];
}
vector<long long> ans(n);
for (int k = 1; k <= n; ++k) {
// 存储所有可能的分割点的差值
vector<long long> diffs;
for (int i = 1; i < n; ++i) {
diffs.push_back(prefixSum[n] - prefixSum[i]);
}
// 对差值进行排序
sort(diffs.begin(), diffs.end(), greater<long long>());
long long sum = prefixSum[n];
for (int i = 0; i < k - 1; ++i) {
sum += diffs[i];
}
ans[k - 1] = sum;
}
return ans;
}
int main() {
int T;
cin >> T;
while (T--) {
int n;
cin >> n;
vector<int> a(n);
for (int i = 0; i < n; ++i) {
cin >> a[i];
}
vector<long long> result = solve(a);
for (int i = 0; i < n; ++i) {
if (i > 0) cout << " ";
cout << result[i];
}
cout << endl;
}
return 0;
}