首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >【51Nod】1021 - 石子归并(区间dp & 四边形不等式优化)

【51Nod】1021 - 石子归并(区间dp & 四边形不等式优化)

作者头像
FishWang
发布2025-08-27 10:14:57
发布2025-08-27 10:14:57
14700
代码可运行
举报
运行总次数:0
代码可运行

点击打开题目

1021 石子归并

基准时间限制:1 秒 空间限制:131072 KB 分值: 20 难度:3级算法题

收藏

取消关注

N堆石子摆成一条线。现要将石子有次序地合并成一堆。规定每次只能选相邻的2堆石子合并成新的一堆,并将新的一堆石子数记为该次合并的代价。计算将N堆石子合并成一堆的最小代价。

例如: 1 2 3 4,有不少合并方法

1 2 3 4 => 3 3 4(3) => 6 4(9) => 10(19)

1 2 3 4 => 1 5 4(5) => 1 9(14) => 10(24)

1 2 3 4 => 1 2 7(7) => 3 7(10) => 10(20)

括号里面为总代价可以看出,第一种方法的代价最低,现在给出n堆石子的数量,计算最小合并代价。

Input

代码语言:javascript
代码运行次数:0
运行
复制
第1行:N(2 <= N <= 100)
第2 - N + 1:N堆石子的数量(1 <= A[i] <= 10000)

Output

代码语言:javascript
代码运行次数:0
运行
复制
输出最小合并代价

Input示例

代码语言:javascript
代码运行次数:0
运行
复制
4
1
2
3
4

Output示例

代码语言:javascript
代码运行次数:0
运行
复制
19

好经典的一个区间dp的题啊!不过这次在纸上画了画,自己都想的差不多了,但是自己只写了个O(n^3)的算法,后来看了看,发现还有一个用四边形不等式优化的方法,也学习了学习,时间优化到了O(n^2)。

好了不废话了,第一次写一个类型题的时候,都会仔细的写一篇讲解的:

要求是把n堆石子合并成一堆,但是只能合并相邻的,所以不能用贪心的方法了。

然后我们考虑简化问题的方法,即:把n-1堆石子合并成一堆求最少耗费,然后再和剩下的一堆合并。

然后再次简化:把n-2堆石子合并成一堆,再和剩下的2堆合并。

……

……

……

最后问题就成了,把2堆石子合并。

然后我们再开始正序往后想,两堆石子的合并想必都会,就是从1 ~ n-1 依次枚举。

那么三堆呢?

比如 1 2 3 三堆,我们可以分开,考虑:dp[1][3] = min(dp[1][2] + dp[3][3] , dp[1][1] + dp[2][3]) + sum[1,3]

那么四堆呢?

我们再分区间 [1,1]+[2,3] 和 [1,2] + [3,4] 和 [1,3] + [4,4] 最小值加上一个sum[1,4]就是移动这四堆所需要的最小耗费。

现在再看是不是清晰了点,那么我我们先看n^3的方法:

三个for:

①枚举长度l:2 ~ n

②枚举起始端点:1 ~ n - l + 1

③枚举断点位置(就是上面的把区间拆开的点的位置):i ~ endd(endd为末端点,等于i + l - 1)

代码如下:(O(n^3))(下面还有优化的算法)

代码语言:javascript
代码运行次数:0
运行
复制
#include <stdio.h>
#include <cstring>
#include <algorithm>
using namespace std;
#define CLR(a,b) memset(a,b,sizeof(a))
#define INF 0x3f3f3f3f
#define LL long long
int main()
{
	int n;
	int t;
	int dp[111][111];
	int sum[111];
	sum[0] = 0;
	scanf ("%d",&n);
	for (int i = 1 ; i <= n ; i++)
		scanf ("%d",&t) , sum[i] = sum[i-1] + t;
	for (int i = 1 ; i <= n ; i++)
		dp[i][i] = 0;
	for (int l = 2 ; l <= n ; l++)		//选取区间长度
	{
		for (int i = 1 ; i <= n-l+1 ; i++)
		{
			int endd = i + l - 1;		//区间末端点
			dp[i][endd] = INF;
			for (int k = i+1 ; k <= endd ; k++)
				dp[i][endd] = min(dp[i][endd] , dp[i][k-1] + dp[k][endd] + sum[endd] - sum[i-1]);
		}
	}
	printf ("%d\n",dp[1][n]);
	return 0;
}

知道了基本的,然后我们来谈优化,优化其实很好想,每次我们找断点k的时候,都是从i ~ endd一个个枚举。我们是否可以用一个方法快速找到呢?答案是可以的,这里有一个四边形不等式:点击打开链接

也就是说,我们用一个数组s[][]记录从 i 到 j 断点的位置,那么下一轮,我们就找这两个断点中间的点:

s[i][j-1] <= k <= s[i+1][j] (注意,这两个s的数值在算 l - 1 的时候已经算出来了)

这个时候这个k就基本确定在那一两个数中了。时间复杂度成功优化,当然多开了一个s数组占用了空间复杂度,但是是没办法的事,本来就是矛盾嘛。

代码如下:

代码语言:javascript
代码运行次数:0
运行
复制
#include <stdio.h>
#include <cstring>
#include <algorithm>
using namespace std;
#define CLR(a,b) memset(a,b,sizeof(a))
#define INF 0x3f3f3f3f
#define LL long long
int main()
{
	int n;
	int t;
	int dp[111][111];
	int s[111][111];
	int sum[111];
	sum[0] = 0;
	scanf ("%d",&n);
	for (int i = 1 ; i <= n ; i++)
	{
		scanf ("%d",&t);
		sum[i] = sum[i-1] + t;
		s[i][i] = i;
	}
	CLR(dp,0);
	for (int l = 2 ; l <= n ; l++)		//选取区间长度
	{
		for (int i = 1 ; i <= n-l+1 ; i++)
		{
			int endd = i + l - 1;		//区间末端点
			dp[i][endd] = INF;
			for (int k = s[i][endd-1] ; k <= s[i+1][endd] ; k++)		//利用四边形不等式优化范围 
			{
				if (dp[i][endd] > dp[i][k] + dp[k+1][endd] + sum[endd] - sum[i-1])		//就算这里k+1超了也不怕
				{
					dp[i][endd] = dp[i][k] + dp[k+1][endd] + sum[endd] - sum[i-1];
					s[i][endd] = k;
				}
			}
		}
	}
	printf ("%d\n",dp[1][n]);
	return 0;
}
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-08-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档