在利用动态规划解决的一些实际问题当中,一类是基于区间上进行的,总的来说,这种区间dp是属于线性dp的一种。但是我们为了更好的分类,这里仍将其单独拿出进行分析讨论。
让我们结合一个题目开始对区间dp的探讨。
凸多边形的最优三角剖分:给定一个具有N个顶点(N ≤ 50)(顶点从1到N编号)的凸多边形,每个顶点的权均已知。问如何把这个 凸多边形划分成N-2 个互不相交的三角形,使得这些三角形顶点的权的乘积之和最小。
其实有一些组合数学底子的读者对这个模型会非常熟悉,笔者在《组合数学——Catalan数》中探讨Catalan数的时候,其实给出了一个类似的模型。
那么我们先着眼于这个问题,首先依然是dp的惯有套路——子问题化。我们设置二维数组f[i,j]记录以第i个顶点到第j个顶点最优方案(i < j),s[m]记录第m个点的权值。那么此时我们只要设置一个参量k,使得k遍历(i,j)的所有值,便把f[i,j]与其更小的子问题建立起了递推关系,即如下的状态转移方程。
for k (i + 1) to (j - 1)
f[i,j] = f[i,k] + f[k,j] + s[i]*s[j]*s[k].
而显然f[1,n]即是我们最终想要全局问题的解。
让我们来看一道与这个模型很类似的题目。(Problem source : hdu 5115)
Matt, an adventurer from the Eastern Kingdoms, meets a pack of dire wolves. There are N wolves standing in a row (numbered with 1 to N from left to right). Matt has to defeat all of them to survive.
Once Matt defeats a dire wolf, he will take some damage which is equal to the wolf’s current attack. As gregarious beasts, each dire wolf i can increase its adjacent wolves’ attack by bi. Thus, each dire wolf i’s current attack consists of two parts, its basic attack ai and the extra attack provided by the current adjacent wolves. The increase of attack is temporary. Once a wolf is defeated, its adjacent wolves will no longer get extra attack from it. However, these two wolves (if exist) will become adjacent to each other now.
For example, suppose there are 3 dire wolves standing in a row, whose basic attacks ai are (3, 5, 7), respectively. The extra attacks bi they can provide are (8, 2, 0). Thus, the current attacks of them are (5, 13, 9). If Matt defeats the second wolf first, he will get 13 points of damage and the alive wolves’ current attacks become (3, 15).
As an alert and resourceful adventurer, Matt can decide the order of the dire wolves he defeats. Therefore, he wants to know the least damage he has to take to defeat all the wolves.
题目大意:给出n个狼的伤害以及每个狼对相邻狼的伤害加成,杀死第i只狼,遭受对应伤害,该狼对相邻狼的攻击加成消失,第i-1和第i+1只狼成为相邻。求解杀死n只狼所受的最小伤害是多少。
数理分析:我们设置a[i]记录第i只狼的伤害,b[i]记录第i只狼对相邻狼的加成效果。
设置dp[i][j]记录杀死第i只到第j只狼所受的最小伤害(之所以这样设置,是呼应区间dp问题中的子问题化)。那么对于j>i时,我们探讨dp[i][j]与其余状态参量有着怎样的递推关系。
我们做类似凸多边形最优三角剖分的分析,对于k∈[i,j],如果基于两个最优子区间dp[i][k-1] , dp[k+1][j],我们得到的一定是"相对"最优的解,很显然嘛两个子区段的最优一定会引导出整体的"相对"最优,而之所以说相对最优,是因为在k取不同值的时候,我们可以从这些相对最优的解组成的集合中找出一个最优解,那么它便是当前最优,这样便可建立较长区间与较短区间的递推关系,即:
dp[i][j] = min(dp[i][k-1] + dp[k+1][j] + a[k] + b[i-1] + b[j+1])
需要注意的是,基于这种状态转移方程的特点,我们计算的时候需要先以区间的长度为指标,即先求解区间长度较短的子问题,这需要我们在编码时枚举子问题做出相应的调整。
参考代码如下。
#include <iostream> #include <cstdio> #include<cstring> #include <algorithm> using namespace std; int a[210],b[210]; int dp[210][210]; int main() { int t,ii,n,i,j,k,l,Min,m; scanf("%d",&t); for (ii=1;ii<=t;ii++) { memset(dp,0,sizeof(0)); scanf("%d",&n); for (i=1;i<=n;i++) scanf("%d",&a[i]); for (i=1;i<=n;i++) scanf("%d",&b[i]); for (i=1;i<=n;i++) { for (j=i;j<=n;j++) dp[i][j]=99999999; } for (l = 0;l <= n;l++) for (i = 1;i < n+1-l;i++) { j = i + l; for (k=i;k<=j;k++) dp[i][j] = min(dp[i][j] , dp[i][k-1] + dp[k+1][j] + a[k] + b[i-1] + b[j+1]); } printf("Case #%d: %lld ",ii,dp[1][n]); } }
让我们再来看一道区间dp。(Problem source : zoj 3469)
When we are focusing on solving problems, we usually prefer to stay in front of computers rather than go out for lunch. At this time, we may call for food delivery.
Suppose there are N people living in a straight street that is just lies on an X-coordinate axis. The ith person's coordinate is Xi meters. And in the street there is a take-out restaurant which has coordinates X meters. One day at lunchtime, each person takes an order from the restaurant at the same time. As a worker in the restaurant, you need to start from the restaurant, send food to the N people, and then come back to the restaurant. Your speed is V-1 meters per minute.
You know that the N people have different personal characters; therefore they have different feeling on the time their food arrives. Their feelings are measured by Displeasure Index. At the beginning, the Displeasure Index for each person is 0. When waiting for the food, the ith person will gain Bi Displeasure Index per minute.
If one's Displeasure Index goes too high, he will not buy your food any more. So you need to keep the sum of all people's Displeasure Index as low as possible in order to maximize your income. Your task is to find the minimal sum of Displeasure Index.
Input
The input contains multiple test cases, separated with a blank line. Each case is started with three integers N ( 1 <= N <= 1000 ), V ( V > 0), X ( X >= 0 ), then N lines followed. Each line contains two integers Xi ( Xi >= 0 ), Bi ( Bi >= 0), which are described above.
You can safely assume that all numbers in the input and output will be less than 231 - 1.
Please process to the end-of-file.
Output
For each test case please output a single number, which is the minimal sum of Displeasure Index. One test case per line.
题目大意:给出一个快递员在数轴上的坐标x,其速度的倒数v,以及需要送货的门户数n,然后给出n户人家在x轴上的坐标以及单位时间不满意度的增幅,请求解该快递员送完n户后最小的不满意度。
数理分析:首先我们将这n+1个位置进行排序,基于最简单的贪心策略,快递员应该送离其最近的人家。即排序后从x处往左或者往右挨个送,而到底往左还是往右,便要进行动态规划的分析了。
子问题化:我们容易看到问题的参数可以放到一个区间[i,j]上,而我们应该注意到对于一个子问题dp[]i[j]来表征快递员送完[i,j]上的人家的最小不满意度,快递员的最终位置是在i还是在j上是表达不同的含义的(这在后面寻求状态转移方程的时候就会发现),因此这里对于最优解的记录,我们有三个维度,即dp[i][j][0]表示区间[i,j]上并以i作为终点的最优解。dp[i][j][1]则表示以j作为终点。
寻求状态转移方程:模拟求解dp[i][j],我们需要分成两种情况。我们设置v[i]记录第i家单位时间不满意度的增幅,x[i]为第i家在数轴上的坐标。
①dp[i][j][0]:这种情况的终点是i,显然,经过i必然经过i-1,因此这里我们对于dp[i][j][0]的求解,需要基于dp[i-1][j][0]、d[i-1][j][1]这两个与其关系最紧密子问题(这里寻求递推关系找当前情况的子问题非常重要,即通过模拟过程找出当前情况前一步必须进行的过程,从而能够建立起状态转移方程)。
那么基于两个最紧密的子问题,我们很容易找到状态转移方程了。
dp[i][j][0] = min(dp[i-1][j][0] + dis(i-1 to i)*(add + v[i])*v , dp[i-1][j][1] + dis(i to j)*(add + v[i])*v)。
同理,对于dp[i][j][1]的求解,可做对称分析。
那么基于这一半的状态转移方程,dis(i to j)表示i到j的距离,add表示总区间上出去[i,j]上的点单位时间不满意度的增幅,v是速度的倒数,那么方程的物理含义就不难理解了。
值得注意的一点是,这道题目在状态转移的过程中每次*v可能会造成数据的溢出,这里采用的技巧是假设v =1,那么状态转移方程可以去掉*v部分,然后基于最终的结果,再乘以v即可。
参考代码如下。
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int inf = 1 <<30; const int maxn = 1005; int dp[maxn][maxn][2] , sum[maxn]; struct node { int x , v; }a[maxn]; bool cmp(node a , node b) { return a.x < b.x; } int f(int l , int r) { if(l > r) return 0; else return sum[r] - sum[l-1]; } int main() { int n , v , x , i , j , k , pos , add; while(scanf("%d%d%d",&n,&v,&x) != EOF) { for(i = 1;i <= n;i++) scanf("%d%d",&a[i].x,&a[i].v); n++; a[n].x = x , a[n].v = 0; sort(a+1 , a+n+1,cmp); for(i =1 ;i<=n;i++) for(j =1 ;j<=n;j++) dp[i][j][0] = dp[i][j][1] = inf; for(i = 1;i<=n;i++) sum[i] = sum[i-1] + a[i].v; for(i = 1;i<=n;i++) if(a[i].x == x) { pos = i; break; } dp[pos][pos][0] = dp[pos][pos][1] = sum[0] = 0; for(i = pos;i>=1;i--) { for(j = pos;j<=n;j++) { add = f(1,i-1) + f(j+1,n); if(i==j) continue; dp[i][j][0] = min(dp[i][j][0] , dp[i+1][j][0] + (add+a[i].v)*(a[i+1].x-a[i].x)); dp[i][j][0] = min(dp[i][j][0] , dp[i+1][j][1] + (add+a[i].v)*(a[j].x-a[i].x)); dp[i][j][1] = min(dp[i][j][1] , dp[i][j-1][1] + (add+a[j].v)*(a[j].x-a[j-1].x)); dp[i][j][1] = min(dp[i][j][1] , dp[i][j-1][0] + (add+a[j].v)*(a[j].x-a[i].x)); } } printf("%d ",min(dp[1][n][0] , dp[1][n][1])*v); } }