合并石子

题目描述

在一圆形操场四周摆放N堆石子 , 现要将石子有次序地合并成一堆.规定每次只能选相临的两堆合并成一堆,并将新的一堆的石子数,记为该次合并的代价。

编一程序,由文件读入堆数N及每堆石子数,

选择一种合并石子的方案,使得做N-1次合并,代价的总和最少

输入

第1行:1个整数n(1<=n<=1000),表示石子的数量

第2行:n个用空格分开的整数,每个整数均小于10000,表示各堆石子的数量。

输出

输入仅一行:1个整数,表示最小的归并代价

样例输入

3
13 7 8

样例输出

43
49

题解

此题仔细想想,石子是不是圆形都一样,都可理解为排成一列,所以可以按输入顺序编号。合并n-1次,即把这些石子合并为一堆。现在就好做一些了

设dp[i][j]表示合并(合并为一堆,下文同)第i堆石子到第j堆石子的最小花费,则:

dp[i][j]=dp[i][k]+dp[k+1][j]+w[i][j](i<=k<=j)

其中,w[i][j]表示第i堆石子到第j堆石子的石子总个数。上述公式可这样理解:合并第i堆到第k堆的最小花费,加上合并第k+1堆到第j堆的最小花费,还要将这两堆合并成一堆,合并这两堆的花费是w[i][j],所以还要加上

w[i][j]。

由于k>=i,所以i要从n循环到1,这样dp[k+1][j]才有值;j就没关系了,i~n和n~i都可以

细节处理:

dp[i][i]=0,w[i][j]可以用前缀和算,节省时间

优化

设s[i][j]为dp[i][j]的最优的k,即

dp[i][j]=dp[i][s[i][j]]+dp[s[i][j]+1][j]+w[i][j]

这个s[i][j]的求法就是朴素算法,只不过多了记录。但这个s[i][j]的取值范围是可以确定的,即:

s[i][j-1]<=s[i][j]<=s[i+1][j]

这个东西证明起来也不难,用数学即可,可以自己思考。当然,也可以先记录,不用,输出来看看满不满足这个规律,满足就可以这样做。实际很多这类的dp都有这样的规律,可以节省时间。但要这样处理一下:

if (s[i][j - 1] == 0)
    s[i][j - 1] = i;
if (s[i + 1][j] == 0)
    s[i + 1][j] = j;

这里相当于朴素算法(以后有值就减少了循环次数),所以要初始化清零。因为这里是s[i][j-1],要让它尽可能有值,所以j从i循环到n。

核心代码:

for(i = n;i >= 1;i--){
	dp[i][i] = 0;
	for(j = i + 1;j <= n;j++){
		if (s[i][j - 1] == 0)
			s[i][j - 1] = i;
		if (s[i + 1][j] == 0)
			s[i + 1][j] = j;
		for(k = s[i][j - 1];k <= s[i + 1][j];k++)
			if (dp[i][j] > dp[i][k] + dp[k + 1][j] + w[i][j]){
				dp[i][j] = dp[i][k] + dp[k + 1][j] + w[i][j];
				s[i][j] = k;
			}
	}
}

 

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值