『经典DP入门』三种石子合并问题

『问题概述』石子合并问题是经典的DP问题。首先它有如下3种题型:1)有N堆石子,现要将石子有序的合并成一堆,规定如下:每次只能移动任意的2堆石子合并,合并花费为新合成的一堆石子的数量。求将这N堆石子合并成 ...
摘要由CSDN通过智能技术生成

『问题概述』


石子合并问题是经典的DP问题。首先它有如下3种题型:

1)有N堆石子,现要将石子有序的合并成一堆,规定如下:每次只能移动任意的2堆石子合并,合并花费为新合成的一堆石子的数量。求将这N堆石子合并成                                                                                                                                                                               

分析:这种情况是最简单的情况,合并的是任意两堆,直接贪心即可,每次选择最小的两堆合并。本质上是使用哈夫曼树算法。

2)有N堆石子,现要将石子有序的合并成一堆,规定如下:每次只能移动相邻的2堆石子合并,合并花费为新合成的一堆石子的数量。求将这N堆石子合并成一堆的总花费最小(大)。

3)问题2的是在石子排列是直线情况下,现在改为环形排列求总花费最小(大)。

所以本次主要攻克的目标是问题2和3。

『线性相邻合并』


例题1. An old Stone Game(POJ 1738)

【题目大意】

有n堆石子(1<=n<=50000)排在一行,每堆石子给定一个重量。要把n堆石子合并成一堆,每次合并只能将相邻的两堆石子合并成一堆,代价为这两堆石子的重量和,求最小的总代价。

【输入】

输入包含几个测试用例。每个测试用例的第一行包含一个整数n,表示堆的数量。之后的n个整数描述了游戏开始时每堆的重量。 n = 0时结束。

【输出】

对于每个测试用例,在单行上输出答案。您可以假设答案不会超过1000000000(一亿)。

【输出样例】

1
100
3
3 4 3
4
1 1 1 1
0

【输出样例】

0
17
8

1.朴素的区间DP方法(O(n^3))


【分析】

题目要求的是一个最优解问题,很容易想到贪心和动态规划。但贪心有明显的错误,所以采用DP,而DP最重要的就是确定状态和找到状态转移方程。题目要求算出总体合并的最小代价,其子问题就是求出在合并的过程中每一步的最小代价。

状态:dp[i][j] - 表示从第i堆到第j堆的合并的最小代价。

那么状态如何转移呢?

通过尝试几个样例发现,每次都是相邻的两堆石子合并,而这两堆石子又是由另外的n堆石子合并而来的。那么我们可以得出一个结论,i到j堆石子最后会成为dp[i][k]和dp[k+1][j](i < k < j)这两堆石子。

所以从 i 到 j 的最小代价就是从 i 到 k 和从 k+1 到 j 的最小代价加上最后一次合并成一堆的代价—— i 到 j 的总石子重量。可以通过枚举k来得到最小代价。

最终的到状态转移方程 :dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]+sum[i][j]);(i != j)。

如何枚举k也是本题的一个问题,因为没有确定 i 和 j 不好枚举 k ,所以采用的方法是枚举j - i -1。

『代码』


#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int n;
int dp[50010][50010];
int sum[50010];
int arr[50010];
int main(){
	
	while(scanf("%d",&n) && n != 0){
		sum[0] = 0;
		for(int i = 1; i <= n; i++){
			for(int j = i; j <= n; j++){
				dp[i][j] = 2e9;
			}
		}
		for(int i =1; i <= n; i++){
			scanf("%d",&arr[i]);
			sum[i] = sum[i-1]+arr[i];
			dp[i][i] = 0;
		}
		if(n == 1){
			printf("0\n");
		}else{
			for(int m = 1; m < n; m++){
				for(int i = 1; i <= n; i++){
					int j = i + m;
					for(int k = i; k <= j; k++){
						dp[i][j] = min(dp[i][j],dp[i][k] + dp[k+
  • 12
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值