CSP 2023 提高级第一轮 CSP-S 2023初试题 完善程序第二题解析

一、题目阅读

(最大值之和)给定整数序列 a0,⋯,an−1,求该序列所有非空连续子序列的最大值之和。上述参数满足 1≤n≤105 和 1≤ai≤108。

一个序列的非空连续子序列可以用两个下标 ll 和 rr(其中0≤l≤r<n0≤l≤r<n)表示,对应的序列为 al,al+1,⋯,ar​。两个非空连续子序列不同,当且仅当下标不同。

例如,当原序列为 [1,2,1,2] 时,要计算子序列 [1]、[2]、[1]、[2]、[1,2]、[2,1]、[1,2]、[1,2,1]、[2,1,2]、[1,2,1,2] 的最大值之和,答案为 18。注意 [1,1] 和 [2,2] 虽然是原序列的子序列,但不是连续子序列,所以不应该被计算。另外,注意其中有一些值相同的子序列,但由于他们在原序列中的下标不同,属于不同的非空连续子序列,所以会被分别计算。

解决该问题有许多算法,以下程序使用分治算法,时间复杂度 O(nlogn)。

试补全程序。

#include <iostream>
#include <algorithm>
#include <vector>

const int MAXN = 100000;

int n;
int a[MAXN];
long long ans;

void solve(int l, int r) {
    if (l + 1 == r) {
        ans += a[l];
        return;
    }
    int mid = (l + r) >> 1;
    std::vector<int> pre(a + mid, a + r);
    for (int i = 1; i < r - mid; ++i) ①;
    std::vector<long long> sum(r - mid + 1);
    for (int i = 0; i < r - mid; ++i)
        sum[i + 1] = sum[i] + pre[i];
    for (int i = mid - 1, j = mid, max = 0; i >= l; --i) {
        while (j < r && ②) ++j;
        max = std::max(max, a[i]);
        ans += ③;
        ans += ④;
    }
    solve(l, mid);
    solve(mid, r);
}

int main() {
    std::cin >> n;
    for (int i = 0; i < n; ++i)
        std::cin >> a[i];
    ⑤;
    std::cout << ans << std::endl;
    return 0;
}

二、程序分析

主要思路‌:‌pre数组是记录对于每个[l,r)后半部分的前i个数的最大值,简单来说就是记录前i的最大值,记录范围在mid~r,max数组是pre数组的前缀和,用于求连续的pre数组和。

分治的过程中,主要考虑(以mid为界)起点终点都在左边的区间;起点终点都在右边的区间;起点在左边终点在右边的区间。

三、题目分析

①处应填(D)

A. pre[i] = std::max(pre[i - 1], a[i - 1])

B. pre[i + 1] = std::max(pre[i],pre[i + 1])
C. pre[i] = std::max(pre[i -1], a[i])
D. pre[i] = std::max(pre[i], pre[i - 1])

【pre数组记录前i个元素的最大值,pre数组本身已经是a数组部分了,找出前i部分的最大值只需要不断与上一个比较】

②处应填(B)

A. a[j] < max

B. a[j] < a[i]
C. pre[j - mid] < max
D. pre[j - mid] > max

【for循环中i是区间左侧,j是区间右侧,while循环是把j定位在:j的左边以i为左侧时可以用max作为区间最大值,j和j的右边不能用max作为区间的最大值。我们可以注意到,j在每次在i-1时没有回到mid,而是选择呆在原地不动,这为了:当i-1后,确保j左边依然可以用max作为最大值,然而,max是在变化的,不能保证j和j的右边都不可以用max作为区间最大值,所以i每减一次,j都要更新一次。这里不和max比,因为max没有更新,如果a[i]变大了,j就会少加,而且还要考虑初值的问题】
③处应填(A)

A. (long long)(j - mid) * max

B. (long long)(j - mid) * (i - 1) * max
C. sum[j - mid]
D. sum[j - mid] * (i - 1)

【我们知道,ans是答案。此处加上的是j前面也就是最大值为max的以i为起点的区间最大值和,那究竟要加多少?我们应该求出j和mid的差,再乘上max就是和】

④处应填(C)

A. (long long)(r - j) * max

B.  (long long)(r - j) * (mid - i) * max
C.  sum[r - mid] - sum[j - mid]
D.  (sum[r - mid] - sum[j - mid]) * (mid - i)

【这里加的是i~j和j之后的区间最大值之和,也就是max算不了的,和max没有什么关系,所以我们排除A和B。再看C和D, 要加的就是pre[j-mid~r-mid(由于sum/pre放的是后半部分,所以访问还要减去mid)用前缀和表示就是“最后减去前部分”,如C]

⑤处应填(A)

A. solve(0, n)

B. solve(0, n - 1)
C. solve(1, n)
D. solve(1, n - 1)

【由于递归结束条件是l + 1 == r,所以solve(0, n)】

看不懂评论区见,大家一起解答,有问题请反应~

很难,看不懂也可以私信。

文章可能有细节没讲完整。🤣

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值