首页 > 代码库 > 区间DP经典模型

区间DP经典模型

http://blog.csdn.net/y990041769/article/details/24238547

先附上一个链接 后面有引用的代码

概述

区间 DP:是指在一段区间上进行的一系列动态规划。

对于区间 DP 这一类问题,我们需要计算区间 [1,n] 的答案,通常用一个二维数组 dp 表示,其中 dp[x][y] 表示区间 [x,y]。

有些题目,dp[l][r] 由 dp[l][r-1] 与 dp[l+1][r] 推得;

也有些题目,我们需要枚举区间 [l,r]内的中间点,由两个子问题合并得到,也可以说 dp[l][r] 由 dp[l][k] 与 dp[k+1][r] 推得,其中 lk<r。

对于长度为 n 的区间 DP,我们可以先计算 [1,1],[2,2][n,n] 的答案,再计算 [1,2],[2,3][n?1,n],以此类推,直到得到原问题的答案。

 

例 1:石子归并问题

题目描述

当前有 N 堆石子,他们并列在一排上,每堆石子都有一定的数量。

我们需要把这些石子合并成为一堆,每次合并都只能把相邻 的两堆合并到一起,

每一次合并的代价都是这两堆石子的数量之和,经过 N-1次合并后成为一堆。求把这些石子合并成一堆所需的最小代价。

解析

根据动态规划的思想,我们只要求出每两堆石子合并的最小代价,然后再求出每三堆石子合并的最小代价,

并以此类推就能最终求出 n 堆石子合并的最小代价。

我们把 dp[i][j] 定义为合并第 i 堆石子到第 j 堆石子所需的最小代价。

很容易就能得到 dp[i][j] = min(dp[i][k] + dp[k+1][j])

显然通过这个式子,我们可以按代价从小到大的顺序进行枚举来不断让石子进行合并,最终就能获得合并成一堆石子的最小代价。

时间复杂度是 O(n?3??)。

memset(dp, 0, sizeof(dp));
for (int l = 2; l <= n; ++l) {
    for (int i = 0, j; i <= n - l; ++i) {
        j = i + l - 1;
        dp[i][j] = inf;
        for (int k = i; k < j; ++k) {
            dp[i][j] = min(dp[i][j], dp[i][k] + dp[k][j]);
        }
    }
}

 

例 2:括号匹配问题

题目描述

当前有一个串,它由 ‘(‘,‘)‘,‘[‘,‘]‘ 四种括号所组成,其中每种括号的次序或数量不定,

求最少向这个串中插入多少个括号就能够使整个串中所有的括号左右配对。如:当前的串为 "( [ ] ] )",向其中添加一个 ‘[‘ 就能达到我们的要求。

解析

我们可以求出当前这个串中,能够一一配对的括号的总数,这里称之为最大匹配,然后再用串的总长度减去最大匹配就能够得到我们需要的答案。

如:当前的串为 "( [ ] ] )",它的最大匹配就是 4

因此,现在我们的问题就转变成了如何求出当前这个串的最大匹配数目。我们可以用 dp[i][j] 来表示从第 个括号到第 个扩号之间的最大匹配数目。

然后根据第 i 个和第 j 个括号是否匹配,以及 dp[i][j] 的值很容易的得出 dp[i+1][j?1]了。

我们可以枚举所有的 dp[i][j],来求出这个串的最大匹配数,再用这个串的总长度 - 最大匹配数就是我们所求的答案。

在枚举过程中:

 

dp[i][j] = judge(i,j) ? dp[i+1][j-1] + 2 : dp[i + 1][j - 1]; //judge()函数功能为判断第i个括号与第j个括号是否匹配,若匹配返回 `true` ,否则返回 `false`。
 

并且 dp[i][j] 也可以通过把区间 [i,j],分成区间 [i][k] 和区间 [k+1][j] 来得到,即:dp[i][j] = dp[i][k] + dp[k+1][j] 。我们枚举 k ,取最大值即可。

状态转移方程为:dp[ i ][ j ] = max ( dp [ i ][ j ] , dp [ i ][ k ] + dp [ k+1 ][ j ] )

 引用的代码
#include <iostream>  
#include <cstring>  
#include <algorithm>  
#include <string>  
using namespace std;  
const int  N = 120;  
int dp[N][N];  
int main()  
{  
    string s;  
    while(cin>>s)  
    {  
        if(s=="end")  
            break;  
        memset(dp,0,sizeof(dp));  
        for(int i=1;i<s.size();i++)  
        {  
            for(int j=0,k=i;k<s.size();j++,k++)  
            {  
                if(s[j]==(&&s[k]==) || s[j]==[&&s[k]==])  
                    dp[j][k]=dp[j+1][k-1]+2;  
                for(int f=j;f<k;f++)  
                    dp[j][k]=max(dp[j][k],dp[j][f]+dp[f+1][k]);  
            }  
        }  
        cout<<dp[0][s.size()-1]<<endl;  
    }  
    return 0;  
}  

 

 

例 3:整数划分问题

题目描述

给你一个长度为 n 的数字序列,要求在这个数字序列中加入 m-1 个乘号,使得这个式子的结果最大。

解析

定义 dp[i][j] 为到第 i 个数结尾共加入 j-1 个乘号所得到的最大值。

我们依次枚举 i,j 计算 dp[i][j],即可得到最后的答案。

状态转移方程为:dp[i][j]=max(dp[i][j],dp[k][j?1]×a[k+1][i]),其中 a[i][j] 表示数字序列中从第 i 个数到第j 个数连起来的值。

memset(dp, 0, sizeof(dp));
for (int i = 0; i < n; n++) {
    dp[i][1] = a[1][i];
}
for (int j = 2; j <= m; j++) {
    for (int i = j; i < n; i++) {
        dp[i][j] = a[i][i];
        for (int k = 1; k < i; k ++) {
            dp[i][j] = max(dp[i][j], dp[k][j - 1] * a[k + 1][i]);
        }
    }
}
printf("%lld\n", dp[n - 1][m]);

 

区间DP经典模型