题目链接: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 (个人感觉代码写的特别通俗易懂)
我的解题思路:
一、这题是经典的区间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; }
要是还不懂,那就拿着演草纸,对着代码,把过程自己算一遍。