NYoj - 737 - 石子合并(一)最详细的详解(区间DP入门题)


石子合并(一)

时间限制: 1000 ms  |  内存限制: 65535 KB
难度: 3
描述
    有N堆石子排成一排,每堆石子有一定的数量。现要将N堆石子并成为一堆。合并的过程只能每次将相邻的两堆石子堆成一堆,每次合并花费的代价为这两堆石子的和,经过N-1次合并后成为一堆。求出总的代价最小值。
输入
有多组测试数据,输入到文件结束。
每组测试数据第一行有一个整数n,表示有n堆石子。
接下来的一行有n(0< n <200)个数,分别表示这n堆石子的数目,用空格隔开
输出
输出总代价的最小值,占单独的一行
样例输入
3
1 2 3
7
13 7 8 16 21 4 18
样例输出
9
239

先说说,自己对区间DP的理解,区间DP,就是在某一区间内满足某个性质,比如最简单的最大最小,一般区间dp有明显的区间性,区别一些线性DP,线性DP每个状态都由前一个转移而来,区间dp也是,但是是由前面区间转移而来,区间dp一般问的是某个区间的某个性质,区间dp从区间是1,是2,是3一步一步转化过来,区间为2就是两个区间为1想加,这样所有区间为2的都就转移出来,如果区间为4的,可能是区间1根区间3,也可能是区间2跟区间2,因为区间1区间2区间3所有情况都枚举过,所以直接枚举转移就好。。简单的区间dp代码有很强的套路性。。

区间DP第一题,在网上查了一些好的题解:

区间型DP一般(也有例外)都是从小的区间开始求最优解,然后不断扩大所求的区间,而求大区间时所用到的小区间前面已经求过了。so直接用就行啦。

区间内枚举最后一次的位置, 所以说区间动规一般都是三层for循环, 前两层用来控制区间长度, 最后一层用来枚举最后一次的位置, 还有需要注意的是区间用从小到大, 因为动态规划就是后面的用到前面的出的结果递推后面的结果。 dp[i][j] 表示从第 i 堆合并到第 j 堆的最小代价,

sum[i][j] 表示第 i 堆到第 j 堆的石子总和,则动态转移方程:dp[i][j] = min(dp[i][j], dp[i][k] + dp[k + 1][j] + sum[i][j]) (i <= k <= j - 1)。

//关键的一块::合并i到j的所有石子。那前一状态一定是两堆石子。
//这步我们就枚举所有可能的位置(两堆石子分开的位置)  
for(int k = i; k < j; k++)
 {
     if(dp[i][j] > dp[i][k] + dp[k+1][j] + sum[i][j])
         dp[i][j] = dp[i][k] + dp[k+1][j] + sum[i][j];
 }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

额。。举个例子吧:4个数(1,2,3, 4) 
某区间(i到j)相距为1时 d = 1 可求出f[1][2] = 3; f[2][3] = 5; f[3][4] = 7; 
d = 2时 , f[1][3] = min(f[1][2] + f[3][3], f[1][1] + f[2][3])+sum[1][3]= 9; (这里f[3][3] = 0,应为合并自己没花费)。同理f[2][4] = 14; 
d = 3时:f[1][4] = 19; 
枚举前一状态 f[1][4] = min(f[1][1]+f[2][4], f[1][2]+f[3][4], f[1][3] + f[4][4]) + sum[1][4];到这有点眉目没。

耐心点看看!! 
还有一点需要注意, 他的最后结果是用的总代价, 所以dp的结果要来自合并当前这次的代价和 当前这次以前的总代价。

第二篇

描述 有N堆石子排成一排,每堆石子有一定的数量。现要将N堆石子并成为一堆。合并的过程只能每次将相邻的两堆石子堆成一堆,并将新的一堆石子数记为该次合并的得分。

  给组测试数据 

     输入   4    表示有4堆石子

       4 4 5 9  表示每堆石子的个数

     输出  54         表示最大的得分   

  分析:主要思想就是一个一个累加:4 4 5 9 先看下去是我想知道dp[i][j]的最大值,i表示起始位置,j表示终止位置,所以我肯定是想求出dp[1][4]间的最大值NYoj - 737 - 石子合并(一)最详细的详解(区间DP入门题)_第1张图片但是我从1到4可是如图这三种截取方法,所以我先从小的开始记录。

dp[1][1]=4;dp[2][2]=4;dp[3][3]=5;dp[4][4]=9。然后我在长度为2的时候记录,dp[1][2]=4+4=8,dp[2][3]=8+5=14;dp[3][4]=14+9=23;这样加起来的值就是8+14+23=45;但是如果我反过来呢?dp[1][2]=5+9=14;dp[2][3]=14+4=18;dp[3][4]=18+4=22;这样加起来的值就是14+18+22=54。很明显,54就是所求的最大值。

NYoj - 737 - 石子合并(一)最详细的详解(区间DP入门题)_第2张图片如图,如果我相求圈着的这个三个的值,我完全可以有图上这两种分割,并且分割出来的值是已经知道的了。

动态规划的思想:先两两合并,在三三合并,最后再NN合并,在合并过程中,较大的区间可以拆分成已经的小区间进行计算,省时又省力。比如,我可以在三三合并的时候可以拆分成一一还有二三相加。即把当前阶段的合并方法细分成前一阶段已计算出的方法,选择其中的最优方案。

具体来说我们应该定义一个数组dp[i,j]用来表示合并方法,i表示从编号为i的石头开始合并,j表示所求区间的结尾,sum表示的是石头的数量。

 

对于上面的例子来说,

   第一阶段:dp[1][1],dp[2][2],dp[3][3],dp[4][4] 因为一开始还没有合并,所以这些值应该全部为0。

    第二阶段:两两合并过程如下,其中sum(i,j)表示石头的数量,即从i开始数j个数的和

 

              dp[1,2]=dp[1,1]+dp[2,2]+sum[1,2];

     dp[2,3]=dp[2,2]+dp[3,3]+sum[2,3];

     dp[3,4]=dp[3,3]+dp[4,4]+sum[4,4];

 

 

 

    第三阶段:三三合并可以拆成两两合并,拆分方法有两种,前两个为一组或后两个为一组

 

         dp[1,3]=dp[1,2]+dp[3,3]+sum[1,3]或dp[1,3]=dp[1,1]+dp[2,3]+sum[1,3];取其最优

    dp[2,4]=dp[2,2]+dp[3,4]+sun[2,4]或dp[2,4]=dp[2,3]+dp[3,3]+sum[2,4];取其最优

    第四阶段:四四合并的拆分方法用三种,同理求出三种分法的得分,取其最优即可。以后第五阶段、第六阶段依次类推,最后在第六阶段中找出最优答案即可。

   动态方程为dp[i][j]=dp[i][k]+dp[k+1][j]+sum[i][j];



#include 
#include 
#include 
#include 
using namespace std;
const int maxn = 250;
const int INF = 0x7fffff;
int dp[maxn][maxn], sum[maxn][maxn], a[maxn], n;
int main()
{
    while(~scanf("%d", &n))
    {
//        memset(dp, 0x7fffff, sizeof(dp));
        for(int i = 1; i <= n; i++)
            scanf("%d", &a[i]), sum[i][i] = a[i];
        for(int i = 1; i <= n; i++)
            for(int j = i; j <= n; j++)
            {
                dp[i][j] = i == j ? 0 : INF;
            }
        for(int len = 1; len < n; len++)
        {
            for(int i = 1; i+len <= n; i++)
            {
                int j = i+len;
                for(int k = i; k < j; k++)
                {
                    sum[i][j] = sum[i][k] + sum[k+1][j];
                    dp[i][j] = min(dp[i][j], dp[i][k]+dp[k+1][j]+sum[i][j]);
                }
            }
        }
        cout << dp[1][n] << endl;
    }
    return 0;
}

区间动态规划问题一般都是考虑,对于每段区间,他们的最优值都是由几段更小区间的最优值得到,是分治思想的一种应用,将一个区间问题不断划分为更小的区间直至一个元素组成的区间,枚举他们的组合 ,求合并后的最优值。
设F[i,j](1<=i<=j<=n)表示区间[i,j]内的数字相加的最小代价
最小区间F[i,i]=0(一个数字无法合并,∴代价为0)

每次用变量k(i<=k<=j-1)将区间分为[i,k]和[k+1,j]两段

For p:=1 to n do // p是区间长度,作为阶段。 
for i:=1 to n do // i是穷举的区间的起点
begin
j:=i+p-1; // j是 区间的终点,这样所有的区间就穷举完毕
if j>n then break; // 这个if很关键。
for k:= i to j-1 do // 状态转移,去推出 f[i,j]
f[i , j]= max{f[ i,k]+ f[k+1,j]+ w[i,j] } 
end; 
这个结构必须记好,这是区间动态规划的代码结构。


你可能感兴趣的:(区间dp,ACM)