NYOJ737 石子合并(区间DP)

来源:互联网 发布:疯狂java讲义第5版pdf 编辑:程序博客网 时间:2024/06/05 18:52

给出区间DP的模板:

区间动态规划问题一般都是考虑,对于每段区间,他们的最优值都是由几段更小区间的最优值得到,是分治思想的一种应用,将一个区间问题不断划分为更小的区间直至一个元素组成的区间,枚举他们的组合 ,求合并后的最优值。
设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 l:=1 to n do // l是区间长度,作为阶段。
for i:=1 to n do // i是穷举的区间的起点
begin
j:=i+l-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;
这个结构必须记好,这是区间动态规划的代码结构。

描述

有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一般(也有例外)都是从小的区间开始求最优解,
然后不断扩大所求的区间,而求大区间时所用到的小区间前面已经求过了。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)。

举个例子吧: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的结果要来自合并当前这次的代价和 当前这次以前的总代价。

AC:

#include<iostream>#include<string.h>using namespace std;#define N 210#define inf 0x3f3f3fint min(int a,int b){return a<b?a:b;}int i,j,k,len,n,sum[N][N],a[N],dp[N][N];int main(){    while(cin>>n){        memset(sum,0,sizeof(sum));        memset(a,0,sizeof(a));        memset(dp,0,sizeof(dp));        for(i=1;i<=n;i++)            cin>>a[i];        for(i=1;i<=n;i++){            for(j=i;j<=n;j++){                for(k=i;k<=j;k++)                    sum[i][j]+=a[k];            }        }        for(i=1;i<=n;i++)            dp[i][i]=0; //自己和自己合并没有花费        for(len=2;len<=n;len++){ //区间长度            for(i=1;i<=n-len+1;i++) { //起点                j=i+len-1;   //终点                dp[i][j]=inf;                for(k=i;k<j;k++)  //k表示分割区间                    dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]+sum[i][j]);            }        }        cout<<dp[1][n]<<endl;    }return 0;}
原创粉丝点击