AHOI2023 ACSPJ 组第四题

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) 的情况多维护一点东西即可,但不是考察重点。

这题解上的粗体是有争议的点

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值