1274--合并石子---区间DP----最全详解

题目链接:http://ybt.ssoier.cn:8088/problem_show.php?pid=1274

【题目描述】

在一个操场上一排地摆放着N堆石子。现要将石子有次序地合并成一堆。规定每次只能选相邻的2堆石子合并成新的一堆,并将新的一堆石子数记为该次合并的得分。

计算出将N堆石子合并成一堆的最小得分。

【输入】

第一行为一个正整数N (2≤N≤100);

以下N行,每行一个正整数,小于10000,分别表示第i堆石子的个数(1≤i≤N)。

【输出】

一个正整数,即最小得分。

【输入样例】

7
13
7
8
16
21
4
18

【输出样例】

 

 

从中午拿到题就开始在网上搜怎么写,一直到晚上才算理解,网上好多大佬写的并不太详细,自己就一直搜,一直看,等到最后才算找到两个大佬感觉比较容易理解的,附上链接

1.https://blog.csdn.net/hf19931101/article/details/46669679?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task   (个人感觉代码写的特别通俗易懂)

2.https://blog.csdn.net/qq_40128883/article/details/88675381?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task    (个人感觉思路讲的特别好)

我的解题思路:

一、这题是经典的区间dp,要求最后合成一堆后得分最少(最多其实也是类似的),肯定要写动态转移方程,首先考虑最后一步的情况,即两大堆合成一堆,只要能保证两大堆的是最优解,那合成一堆和也是最少的得分。那两大堆怎么最优呢,必然也是合成大堆的子问题最优.....(符合动态规划,原问题可以拆成子问题)

二、

       2.1.假如一开始有一堆石子,那最少得分就为0。

       2.2.假如一开始有两堆石子,那就只有一种情况,即最少得分就为两堆石子的和。

       2.3.假如一开始有三堆石子,那就有两种情况,比较一下 第一堆+第二堆 与 第二堆+第三堆 谁小,然后与剩下的一堆合并。

       2.4.假如有一开始四堆石子,那就有三种情况,不过我看网上基本没有展开分析的,我认为这一步展开分析对理解状态转移方程很有必要,假设现在有四堆石子,分别为3、4、5、6,下面用1,2,3,4代表堆数,小括号里面数字代表先把这几堆合并一块,那三种具体的情况就是1,(2+3+4).。。。。(1+2),(3+4) 。。。。(1+2+3),4

一共就分这三种情况,把这三种情况都求出来就可以找到最优解了 ,就如这三种情况里面的第一种1,(2+3+4)。首先计算(2+3+4)这三堆,那这就符合上面情况3了(一开始有三堆石子那个),剩下的计算我相信你也会了,

      2.5.假如一开始有五堆石子,那就有四种情况,假设堆数分别为1,2,3,4,5,那就有这四种情况1,(2+3+4+5)。。。。(1+2),(3+4+5)。。。。(1+2+3),(4+5)。。。。(1+2+3+4),5。。算的话按照上面的算就行了,我认为写的足够详细了,如果还是不太理解,可以多看几遍。。

      2.6.假如一开始有n堆石子,那就有n-1中情况。。。。我相信这n-1中情况你肯定都知道了。

上面看完了,你也对展开情况有了一定的认识,可能会认为还有一些疑问,假设还是那四堆石子,个数为3、4、5、6,我要求最小得分,计算过程就是((3+4)+(5+6))*2=36,那要按上面写的过程分析,感觉好麻烦,会不会多此一举呀。

这里我告诉你,绝对不会,因为个人在计算时是肯定会选择最优的方法计算,而计算机不知道先算哪几个,后算哪几个,

假如给了四五堆石子,按照上面讲的思路分析,你想计算出最后合成一堆的最小得分,那就是两大堆合成的最后一堆的最优解,那两大堆都是啥我也不知道,也没法加呀。其实上面的就是一种展开思路,写代码计算的话不会那样计算(要是按上面的思路计算,估计要用递归了吧),计算的话会从底层计算,一步步的求出答案,和这个类似的思路就是数塔   http://acm.hdu.edu.cn/showproblem.php?pid=2084     ,,,,这是最简单并典型的DP了,他的计算过程就是从下到上,一步步的计算,最后求出来答案。

三、下面说该怎样写代码,具体写的话就应该类似数塔那样,从下到上开始写

      3.1.假如一开始有n堆石子,这时候就可以算出来有第一堆和第二堆合并值、第二堆和第三堆的合并值,,,,,,第n-1堆和第n堆的合并值(根据已知条件可以求得)。

       3.2.下面就要求第一堆到第三堆合并的最小值、第二堆到到第四堆合并的最小值,,,,,,,第n-2到第n堆合并的最小值(全部都是三堆合并成一堆),就像第一个,求第一堆到第三堆合并的最小值时(这就和上面讲的一样,一共有三种情况,分别是 1,(2+3)。。。。(1+2),3)他就会用到上一步求解的第一堆和第二堆的合并值,第二堆和第三堆的合并值。其他堆得计算方法也是和这三堆的计算方法类似,其中的共同点就是会用到3.1中已经求出来的

      3.3 下面就要求第一堆到第四堆合并的最小值、第二堆到第五堆合并的最小值,,,,,,,,第n-3堆到第n堆合并的最小值(这一步都是四堆合并成一堆的),具体计算方法就不再多少了,这里面求的时候肯定也用到了3.2求出来的了.

     3.4 这个最后一步就是求第一堆到第n堆的最小值(就是题目要求的),求这一步的时候也是和上面的计算方法类似,利用上面一步步求出来的论进一步就可以求出来这一步的结论了

这个计算过程其实就是动态规划的一个特性-------备忘录,每一步求出来的都会存起来,下一步求解时会利用上一步的结论,一步一步的推到出来答案

动态转移方程:

                       

这个就是动态转移方程,dp[i][j]代表的就是从i堆到j堆合并的最小值。

还需要定义一个数组sum,用来存储前i堆石子的总和,

又定义了一个temp变量,他具体有啥用看下去就行了

假设现在有四堆石子,现在如果要求前三堆石子合并的最小值,那就是在前三堆中选出来相邻两堆和的最小值,再加上另外一堆,然后再加上这三堆的总和就是这三堆和的最小值。假如要求后三堆的最小值,计算方法也是选出来相邻两堆和的最小值,再加上另外一堆,然后再加上后三堆的总和。上面的temp变量就是用来求最后加上的数的。

一个具体的样例讲解:

我相信如果你把上面讲的看懂了,那这一题你就会的差不多了,下面再来一个例子讲解一下。

其实计算的一步步过程(先两堆放一块求,在求三堆、四堆,,,直到n堆)就是一个填表的过程,

假设现在有四堆石子,分别为3,4,5,6.

那他就分三大步(等于石子总堆数减一)

第一大步,两个两个都合并(这个样例里面一共要两两合并3次),第二大步,三个三个合并(一共要合并2次),第三大步,四堆合成一堆

从上面可以看出第i大步都比第i-1大步合并的次数少一,

上面这个图片就是完成的填表过程,要是现在还不太懂,就可以自己动手填一遍表,下面放代码

代码

#include<iostream>
#include<algorithm>
using namespace std;
int main()
{
	int n,a[105],sum[105]={0},dp[105][105];
	cin>>n;
	for(int i=0;i<n;i++)
		cin>>a[i];
	sum[0]=a[0];
	for(int i=1;i<n;i++)
		sum[i]=sum[i-1]+a[i];
	
	for(int i=0;i<n;i++)
		dp[i][i]=0;
	for(int d=1;d<n;d++)//一共几大步 
	{//每一大步都是沿着对角线填表的 
		for(int i=0;i<n-d;i++)//填表时每一个格的横坐标 
		{
			int j=i+d;//每一个格的纵坐标 
			dp[i][j]=99999999;
			int temp=sum[j]-(i>0?sum[i-1]:0);//最后需要加上的值 
			for(int k=i;k<j;k++)
				dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]+temp);
		}
	}
	cout<<dp[0][n-1]<<endl;
	return 0;
}

要是还不懂,那就拿着演草纸,对着代码,把过程自己算一遍。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值