AHOI2023 ACSPJ 组
第四题
题目
小可可面前有 n n n 堆石子,第 i i i 堆石子有 a i a_i ai 个石子。小可可想要在开始选择一堆石子,然后从它开始,每次合并这堆石子左边的那堆石子或者右边的那堆石子。合并两堆石子个数为 x x x, y y y 的石子堆需要花 x + y x + y x+y 的力气,并且会合并成一堆 x + y x + y x+y 个石子的石子堆。
小可可想花费最小的力气从最初选择的那堆石子开始,将所有石子都合并完。小可可想知道,如果他选择编号在 l . . . r l...r l...r 里面的每一堆石子作为最初的石子,那么他将 n n n 堆石子合并成一堆花的最小力气是多少。
小可可不想太为难你,所以他保证所有的 a i a_i ai 是随机的。
60 60 60 分做法
记忆化搜索:
#include <iostream>
#define int long long
int N, L, R;
int Arr[5005], SumArr[5005], NoteBook[5005][5005];
int Sum(int l, int r) {
//从l到r的和
return SumArr[r] - SumArr[l - 1];
}
int DFS(int l, int r) {
//已合并区间是[l, r]
int Min = 0x7F7F7F7F7F7F7F7F; //最小值
if (NoteBook[l][r] != 0) {
//记忆化(不加20分)
return NoteBook[l][r];
}
if (l - 1 >= 1) {
//左边还有,合并左边
Min = std::min(Min, DFS(l - 1, r) + Sum(l, r) + Arr[l - 1]);
}
if (r + 1 <= N) {
//右边还有,合并右边
Min = std::min(Min, DFS(l, r + 1) + Sum(l, r) + Arr[r + 1]);
}
if (l == 1 && r == N) {
//边界条件:都合并好了
Min = 0;
}
NoteBook[l][r] = Min; //记录
return Min;
}
signed main() {
std::cin >> N >> L >> R;
for (int i = 1; i <= N; i++) {
std::cin >> Arr[i];
SumArr[i] = SumArr[i - 1] + Arr[i]; //前缀和
}
for (int i = L; i <= R; i++) {
std::cout << DFS(i, i) << ' '; //输出(一开始已合并区间是从i到i的)
}
std::cout << '\n';
return 0;
}
满分做法
题解(有争议):
先考虑 l = r l = r l=r 的情况,即给定一个点,每次向左向右合并的最小花费。
贪心地去考虑,如果在还没有合并的点中,最小值就在当前已经合并的区间的左边或者右边,那一定会先合并掉这个最小值。
但如果最小值不在它的左边或者右边呢?
比如最小值在它的左边,当最小值右边的点被合并之后,下一步一定是将这个最小值合并起来。
于是我们可以把最小值和它右边的合起来看成一个点,因为它们一定是紧接着一起合并的。
那么当目前合并的区间和合起来的若干个点合并起来时,设合起来的这若干个点有 x x x 个,那么耗费力气就是 x × x \times x× 合并区间的 a i a_ i ai 的和加上合起来的若干个点内部的贡献,即将它们一个一个合并起来,它们的 a i a_i ai 产生的贡献。
合起来的点之间也是可以类似 a i a_i ai 一样,比较贪心下我们按照之前想合并起来的先后顺序的。比较两个合起来的点 x x x, y y y 就是比较 x x x 先合并到最初点的花费和 y y y 先合并到最初点的花费。
之后能得到如果 x x x 内部点 a i a_i ai 的平均值较小,那么 x x x 先合并就更优。这时如果 y y y 左边是 x x x,初始点在 y y y 的右边,那么就可以将 x x x, y y y 合并起来,因为合并 y y y 之后一定立刻合并 x x x。
于是对于初始点左边的点,从左往右扫并且维护一个栈。每次加一个点到栈顶,如果栈顶的平均值大于栈顶下一个的平均值,就把栈顶和栈顶下一个合并起来。
对于初始点右边的点,从右往左这么做就行。
这样考虑初始点左边得到的栈和右边得到的栈,它们的平均值从栈顶到栈底递增。
每次就选左边栈顶和右边栈顶平均值较小的那个合并起来就行。计算花费是容易的。
这样一个点就做到了 Θ ( n ) \Theta(n) Θ(n) 的复杂度。
由于数据随机,可以证明这个栈的大小是 Θ ( l o g n ) \Theta(log n) Θ(logn) 的,所以对于一个点,它左边的栈和右边的栈都只有 Θ ( l o g n ) \Theta(log n) Θ(logn) 个元素,直接对每个位置,将它左边/右边的栈记录下来,询问的时候像上面那样做就行。
时空复杂度均为 Θ ( n l o g n ) \Theta(n log n) Θ(nlogn)。
其实数据不随机,即栈大小为 Θ ( n ) \Theta(n) Θ(n) 的情况多维护一点东西即可,但不是考察重点。
这题解上的粗体是有争议的点