概论
所谓区间DP,从名字也可以看出与区间是密不可分的,也就是通过动态规划求得一段区间上最优解的算法,它的主要思想就是先在小区间进行DP得到最优解,然后再利用小区间的最优解合并求大区间的最优解。
一般算法格式
区间DP的算法格式比较固定,一般由数组dp[i][j]来表示在区间[i,j]内的最优解(具体由题目定义),然后由一个整形k(i<=k<j)来对区间[i,j]进行分割,一般分割为[i,k]和[k+1,j]两个区间,根据题目来确定它的状态转移方程。
for (int i = 1; i <= n; i++) //区间长度为1的初始化
dp[i][i] = x; // x视情况而定
for (int len = 2; len <= n; len++) //枚举区间长度
{
for (int i = 1, j = len; j <= n; i++, j++) //区间[i,j]
{
//DP方程实现
}
}
例题
问题 Y: 【动态规划】合并魔法石I
时间限制: 1 Sec 内存限制: 64 MB
题目描述
太空梯的能量导轨上有n堆魔法石排成一排,其编号为1,2,3,…,n(n≤100)。每块魔法石有一定的数量,例如有7堆魔法石,分别为13,7,8,16,21,4,18。
现在要将n堆魔法石归并成为一堆。归并的过程为每次只能将相邻的两堆魔法石堆成一堆,这样经过n-1次归并后成为一堆,如上面的7堆魔法石,可以有多种方法归并成一堆,其中的两种方法如图所示。
则(a)的归并代价为20+24+25+44+69+87=267
(b)的归并代价为15+37+22+28+59+87=248
可见不同的过程得到的归并代价是不一样的,现在请编程找出一种合理的方法,使归并代价最小。
输入
第一行为一个整数n,且1<n≤100,表示有n堆沙子,第二行为n堆魔法石的数量。
输出
一个整数,即最小代价。
样例输入
7
13 7 8 16 21 4 18
样例输出
239
题目链接:http://exam.upc.edu.cn/problem.php?cid=1428&pid=24
分析:
这道题和区间DP的经典例题石子合并如出一辙,也都是将n堆石子通过合并为一堆求最后的最小花费。
要求n堆石子合并,根据动态规划的思想,我们可以将它划分为子问题n-1堆石子的合并,通过子问题求得最优解来一步步推导出最终的最优解,我们可以先求出2堆石子合并的花费,然后三堆石子合并可以分解为两堆石子与另一堆石子合并,这样通过前面求得的子问题最优解,最终就可以求得n堆石子合并所需的最小花费了。
设dp[i][j]为从第i堆石子到第j堆石子的合并,那么dp[i][j]就可以通过k来划分为dp[i][k]+dp[k+1][j]的问题了,这时候我们就可以写出它的状态转移方程了dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]+sumij),sumij为区间[i,j]内合并石子的总花费,可以提前算出来减少时间复杂度(前缀和思想,牺牲空间获取时间)。
代码如下:
#include<bits/stdc++.h>
using namespace std;
int dp[101][101];
int sum[101];
int main()
{
int n,v[101];
cin>>n;
memset(sum,0,sizeof(sum));
memset(dp,0x3F,sizeof(dp));
for(int i=1;i<=n;i++){
cin>>v[i];
sum[i]=sum[i-1]+v[i];
dp[i][i]=0;
}
for(int len=2;len<=n;len++){
for(int i=1,j=len;j<=n;i++,j++){
int sumij=sum[j]-sum[i-1];
for(int k=i+1;k<=j;k++){
dp[i][j]=min(dp[i][j],dp[i][k-1]+dp[k][j]+sumij);
}
}
}
// for(int i=1;i<=n;i++){
// for(int j=1;j<=n;j++){
// printf("%15d",dp[i][j]);
// }cout<<endl;
// }
cout<<dp[1][n]<<endl;
return 0;
}