洛古U552155 J-C 序列分段(前缀和)

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=1ki×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 (1kn),都求出并回答上述式子的最大值吧。

输入格式

本题有多组测试数据。

输入的第一行包含一个正整数 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,1N15

对于 60 % 60\% 60% 的数据有: 1 ≤ T ≤ 10 , 1 ≤ N ≤ 200 1 \leq T \leq 10, 1 \leq N \leq 200 1T10,1N200

对于所有的测试数据有: 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 1T100,1N2×105 ,106ai106

思路:以下是针对这道题的解题思路:

核心思路

本题的核心目标是对于给定的序列 a a a,将其分割成 k k k 段( 1 ≤ k ≤ n 1\leq k\leq n 1kn),并使得 ∑ 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;
}

整体流程

  1. 读取测试数据的组数 T T T
  2. 对于每组测试数据:
    • 读取序列的长度 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=1Tni2×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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值