描述:
设有N堆沙子排成一排,其编号为1,2,3,…,N(N<=300)。每堆沙子有一定的数量,可以用一个整数来描述,现在要将这N堆沙子合并成为一堆,每次只能合并相邻的两堆,合并的代价为这两堆沙子的数量之和,合并后与这两堆沙子相邻的沙子将和新堆相邻,合并时由于选择的顺序不同,合并的总代价也不相同,如有4堆沙子分别为 1 3 5 2 我们可以先合并1、2堆,代价为4,得到4 5 2 又合并 1,2堆,代价为9,得到9 2 ,再合并得到11,总代价为4+9+11=24,如果第二步是先合并2,3堆,则代价为7,得到4 7,最后一次合并代价为11,总代价为4+7+11=22;问题是:找出一种合理的方法,使总的代价最小。输出最小代价。
这是一道区间dp的入门题,我先简单谈谈区间dp,区间dp一般是学完背包问题来学的一类问题,许多人第一次见这道题和这道题的标程时,都不知道什么叫区间dp,但会发现一点,这道题的几重循环和以往的不大一样,并不是1到n或者是n到1这种简单的循环了,而是各种奇奇怪怪的限制条件,让人摸不到头脑,没办法阅读代码;就算是强行解释了,过一段时间也就忘了......
所谓区间dp嘛,其实就是在一段区间上执行动态规划,那么如何执行呢?假如给出一段很长的区间,要处理每个点之间的关系,得到最终全局的最优解怎么办呢?
区间dp的思想就是首先找一个长度为1的区间(一般称为元区间)作为初态然后开始处理问题,在处理时,我们
首先找一段长度为2的区间,通过循环使其在总区间上推进,同时处理点与点之间的关系
,然后退回来,把区间长度加一再做上述工作,即每次都找了一段区间,在大区间上移动,从而解决问题;
因此,区间长度便是动态规划的阶段!
可能有的同学曾经迷糊过“动态规划那么多循环,到底哪个放外面,哪个放里面啊!”不急,其实,动态规划分为
阶段、状态和决策,三者应该从外到里循环。
至于什么是阶段状态决策,同学们可以通过刷题看题解慢慢领会。
好,现在我们来处理这道题,一段石子,我们可以看成一段区间!思考一下,如果我要求第1-10个石子和第11-20个石子合并得最小花费,怎么办?可以想到,我们如果要合并这两段区间,前提是这两个区间内部已经完成了合并!好,那子问题就出来了!
我们设f[l][r]是把从
点l到点r的石子合并的最小花费!
然后可以得到转移方程,f[l][r]=min(f[l][r], f[l][k]+f[k+1][r]);f[l][r]+=sum[r]-sum[l-1]; sum[i]数组表示前i个石子的重量的和;因为要加上本次合并的花费嘛!
最后就是处理循环问题了,已知区间长为阶段,那么状态就是左端点和右端点,而决策就是不断地枚举合并点k的位置,找寻最优解!这道题完美结束!
上述内容部分参考李煜东学长的《算法竞赛进阶指南》
下面上代码!
1 #include<iostream> 2 #include<cstdio> 3 #include<cmath> 4 #include<cstring> 5 #include<cstdlib> 6 #include<string> 7 #include<algorithm> 8 #include<queue> 9 using namespace std; 10 const int inf=0x3f; 11 int f[305][305],sum[305]; 12 int val[305]; 13 int n,m; 14 int main() 15 { 16 scanf("%d",&n); 17 for(int i=1;i<=n;i++){ 18 scanf("%d",&val[i]); 19 } 20 memset(f,inf,sizeof(f)); 21 for(int i=1;i<=n;i++){ 22 f[i][i]=0; 23 sum[i]=sum[i-1]+val[i]; 24 } 25 for(int len=2;len<=n;len++){//阶段,区间长度 26 for(int l=1;l<=n-len+1;l++){//状态左端点 27 int r=len-1+l;//右端点 28 for(int k=l;k<r;k++){//决策,连接点k 29 f[l][r]=min(f[l][k]+f[k+1][r],f[l][r]); 30 } 31 f[l][r]+=sum[r]-sum[l-1];//本次合并的花费 32 } 33 } 34 cout<<f[1][n]<<endl; 35 return 0; 36 }