区间dp模型(石子归并,括号匹配,整数划分)入门经典三道题

区间dp顾名思义就是在一个区间上进行的一系列动态规划。对一些经典的区间dp总结在这里。


1) 石子归并问题


题目链接:http://acm.nyist.edu.cn/JudgeOnline/problem.php?pid=737


描述: 有N堆石子排成一排,每堆石子有一定的数量。现要将N堆石子并成为一堆。合并的过程只能每次将相邻的两堆石子堆成一堆,每次合并花费的代价为这两堆石子的和,经过N-1次合并后成为一堆。求出总的代价最小值。


分析:要求n个石子归并,我们根据dp的思想划分成子问题,先求出每两个合并的最小代价,然后每三个的最小代价,依次知道n个。

定义状态dp [ i ] [ j ]为从第i个石子到第j个石子的合并最小代价。

那么dp [ i ] [ j ] = min(dp [ i ] [ k ] + dp [ k+1 ] [ j ]) 

那么我们就可以从小到大依次枚举让石子合并,直到所有的石子都合并。

这个问题可以用到平行四边形优化,用一个s【i】【j】=k 表示区间 i---j 从k点分开才是最优的,这样的话我们就可以优化掉一层复杂度,变为O(n^2).

代码:

[cpp] view plain copy print ?
  1. #include   
  2. #include   
  3. #include   
  4. #define N 210  
  5. int dp[N][N],sum[N];  
  6. int main()  
  7. {  
  8.     int n;  
  9.     while(~scanf("%d",&n))  
  10.     {  
  11.         int a[N];sum[0]=0;  
  12.         for(int i=1;i<=n;i++){  
  13.             scanf("%d",&a[i]);  
  14.             sum[i]=sum[i-1]+a[i];  
  15.         }  
  16.         memset(dp,0,sizeof(dp));  
  17.         int i,j,l,k;  
  18.         for(l = 2; l <= n; ++l)  
  19.         {  
  20.             for(i = 1; i <= n - l + 1; ++i)  
  21.             {  
  22.                 j = i + l - 1;  
  23.                 dp[i][j] = 2100000000;  
  24.                 for(k = i; k < j; ++k)  
  25.                 {  
  26.                     dp[i][j] = std::min(dp[i][j],dp[i][k] + dp[k + 1][j] + sum[j] - sum[i-1]);  
  27.                 }  
  28.             }  
  29.         }  
  30.         printf("%d\n", dp[1][n]);  
  31.     }  
  32.     return 0;  
  33. }  

平行四边形优化代码:

[cpp] view plain copy print ?
  1. #include   
  2. #include   
  3. #include   
  4. #define N 210  
  5. int dp[N][N],sum[N],s[N][N];  
  6. int main()  
  7. {  
  8.     int n;  
  9.     while(~scanf("%d",&n))  
  10.     {  
  11.         int a[N];sum[0]=0;  
  12.         memset(s,0,sizeof(s));  
  13.         for(int i=1;i<=n;i++){  
  14.             scanf("%d",&a[i]);  
  15.             s[i][i]=i;  
  16.             sum[i]=sum[i-1]+a[i];  
  17.         }  
  18.         memset(dp,0,sizeof(dp));  
  19.         int i,j,l,k;  
  20.         for(l = 2; l <= n; ++l)  
  21.         {  
  22.             for(i = 1; i <= n - l + 1; ++i)  
  23.             {  
  24.                 j = i + l - 1;  
  25.                 dp[i][j] = 2100000000;  
  26.                 for(k = s[i][j-1]; k <= s[i+1][j]; ++k)  
  27.                 {  
  28.                     if(dp[i][j]>dp[i][k] + dp[k + 1][j] + sum[j] - sum[i-1])  
  29.                     {  
  30.                         dp[i][j]=dp[i][k] + dp[k + 1][j] + sum[j] - sum[i-1];  
  31.                         s[i][j]=k;  
  32.                     }  
  33.                 }  
  34.             }  
  35.         }  
  36.         printf("%d\n", dp[1][n]);  
  37.     }  
  38.     return 0;  
  39. }  



2)括号匹配


题目链接:

poj2955,http://poj.org/problem?id=2955

nyoj 15 http://acm.nyist.edu.cn/JudgeOnline/problem.php?pid=15


描述:给出一串的只有‘(’ ‘)’  '[‘  ']'四种括号组成的串,让你求解需要最少添加括号数让串中的所有括号完全匹配。


分析:我们求出这个串的最大匹配,然后串的总长度-最大匹配就是答案。


方法1:首先能想到的是转化成LCS(最长公共子序列),枚举中间点,求所有的LCS中的最大值 * 2就是最大匹配。但是复杂度较高,光LCS一次就O(n^2)的复杂度。


方法2:

首先考虑怎么样定义dp让它满足具有通过子结构来求解、

定义dp [ i ] [ j ] 为串中第 i 个到第 j 个括号的最大匹配数目

那么我们假如知道了 i 到 j 区间的最大匹配,那么i+1到 j+1区间的是不是就可以很简单的得到。

那么 假如第 i 个和第 j 个是一对匹配的括号那么dp [ i ] [ j ] = dp [ i+1 ] [ j-1 ] + 2 ;

那么我们只需要从小到大枚举所有 i 和 j 中间的括号数目,然后满足匹配就用上面式子dp,然后每次更新dp [ i ] [ j ]为最大值即可。

更新最大值的方法是枚举 i 和 j 的中间值,然后让 dp[ i ] [ j ] = max ( dp [ i ] [ j ] , dp [ i ] [ f ] + dp [ f+1 ] [ j ] ) ;


如果要求打印路径,即输出匹配后的括号。见http://blog.csdn.net/y990041769/article/details/24238547详细讲解

代码:

[cpp] view plain copy print ?
  1. #include   
  2. #include   
  3. #include   
  4. #include   
  5. using namespace std;  
  6. const int  N = 120;  
  7. int dp[N][N];  
  8. int main()  
  9. {  
  10.     string s;  
  11.     while(cin>>s)  
  12.     {  
  13.         if(s=="end")  
  14.             break;  
  15.         memset(dp,0,sizeof(dp));  
  16.         for(int i=1;i
  17.         {  
  18.             for(int j=0,k=i;k
  19.             {  
  20.                 if(s[j]=='('&&s[k]==')' || s[j]=='['&&s[k]==']')  
  21.                     dp[j][k]=dp[j+1][k-1]+2;  
  22.                 for(int f=j;f
  23.                     dp[j][k]=max(dp[j][k],dp[j][f]+dp[f+1][k]);  
  24.             }  
  25.         }  
  26.         cout<
  27.     }  
  28.     return 0;  
  29. }  


3)整数划分问题


题目链接:nyoj746 http://acm.nyist.net/JudgeOnline/problem.php?pid=746


题目描述:给出两个整数 n , m ,要求在 n 中加入m - 1 个乘号,将n分成m段,求出这m段的最大乘积


分析:根据区间dp的思想,我们定义dp [ i ] [ j ]为从开始到 i 中加入 j 个乘号得到的最大值。

那么我们可以依次计算加入1----m-1个乘号的结果

而每次放入x个乘号的最大值只需枚举第x个乘号的放的位置即可

dp [ i ] [ j ]  = MAX (dp [ i ] [ j ] , dp [ k ] [ j-1 ] * a [ k+1 ] [ i ] ) ;


代码:

[cpp] view plain copy print ?
  1. #include   
  2. #include   
  3. #define MAX(a,b) a>b?a:b  
  4. long long a[20][20];  
  5. long long dp[25][25];  
  6. int main()  
  7. {  
  8.     int T,m;  
  9.     scanf("%d",&T);  
  10.     getchar();  
  11.     while(T--)  
  12.     {  
  13.         char s[22];  
  14.         scanf("%s",s+1);  
  15.         scanf("%d",&m);  
  16.         int l=strlen(s),ok=1;  
  17.         memset(a,0,sizeof(a));  
  18.         for(int i=1;i
  19.         {  
  20.             if(s[i]=='0')  
  21.                 ok=0;  
  22.             for(int j=i;j
  23.             {  
  24.                 a[i][j]=a[i][j-1]*10+(s[j]-'0');  
  25.             }  
  26.         }  
  27.         if(ok==0&&l-1==m||l-1
  28.         {  
  29.             printf("0\n");continue;  
  30.         }  
  31.         long long x,ans;  
  32.         memset(dp,0,sizeof(dp));  
  33.         for(int i=0;i
  34.             dp[i][1]=a[1][i];  
  35.         ans=0;  
  36.         if(m==1)  
  37.             ans=dp[l-1][1];  
  38.         for(int j=2;j<=m;j++)  
  39.         {  
  40.             for(int i=j;i
  41.             {  
  42.                 ans=a[i][i];  
  43.                 for(int k=1;k
  44.                 {  
  45.                     dp[i][j]=MAX(dp[i][j],dp[k][j-1]*a[k+1][i]);  
  46.                 }  
  47.             }  
  48.         }  
  49.         printf("%lld\n",dp[l-1][m]);  
  50.     }  
  51.     return 0;  


NYOJ 737:http://acm.nyist.net/JudgeOnline/problem.php?pid=737
代码:

#include 
#include 
#include 
#define sf scanf
#define pf printf

using namespace std;
typedef long long LL;
const int maxn = 205;
/*
    题意: N堆石子排成一排 每次可以合并相邻的两堆石子 合并的代价是两堆石子的数量的和
            在经过N - 1次合并和,N堆石子合并为一堆石子,问最小的代价是多少
    DP[i][j] 表示将第i堆和第j堆合并为一堆的最小花费
    那么DP[i][j] 就可以表示成 DP[i][j] = min{DP[i][k] + DP[k + 1][j] + COST((i ~ k):(k + 1 ~ j))}

*/
const LL INF = 0x3f3f3f3f;
LL DP[maxn][maxn];
LL A[maxn];
LL DPS(int l,int r){
    if(DP[l][r] != -1) return DP[l][r];
    LL& ans = DP[l][r];
    ans = INF;
    LL L = 0,R = 0;
    for(int i = l;i <= r;++i) R += A[i];
    for(int k = l;k < r;++k){
        L += A[k],R -= A[k];
        ans = min(ans,L + R + DPS(l,k) + DPS(k + 1,r));
    }
    return ans;
}
int main(){
    int n;
    while( ~sf("%d",&n) ){
        for(int i = 0;i < n;++i) sf("%lld",A + i);
        memset(DP,-1,sizeof(DP));
        for(int i = 0;i < n;++i) DP[i][i] = 0;
        DPS(0,n - 1);
        pf("%lld\n",DP[0][n - 1]);
    }
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43

POJ 2955:http://poj.org/problem?id=2955
代码:

#include 
#include 
#include 
#define sf scanf
#define pf printf
using namespace std;
/*
    题意:给一个只包含‘[’,‘]’,‘(’,‘)’的字符串求在这个字符串的一个最长的子串 是一个合法括号字符
    DP[i][j] 表示将i 到 j 变为合法字符串要删除的最少字符数
    那么 DP[i][j] = min{DP[i][k] + DP[k + 1][j]};check(s[i],s[j]) DP[i][j] = min(DP[i][j],DP[i + 1][j - 1]);
*/
const int INF = 0x3f3f3f3f;
const int maxn = 105;
int DP[maxn][maxn];
char s[maxn];
int DPS(int l,int r){
    if(~DP[l][r]) return DP[l][r];
    int& ans = DP[l][r];
    ans = INF;
    if(s[l] == '(' && s[r] == ')' || s[l] == '[' && s[r] == ']') ans = min(ans,DPS(l + 1,r - 1));
    for(int k = l;k < r;++k){
        ans = min(ans,DPS(l,k) + DPS(k + 1,r));
    }
    return ans;
}


int main(){
    while(sf("%s",s)){
        if(s[0] == 'e') break;
        int len = strlen(s);
        memset(DP,-1,sizeof(DP));
        for(int i = 0;i < len;++i){
            DP[i][i] = 1;
            DP[i + 1][i] = 0;
        }
        pf("%d\n",len - DPS(0,len - 1));
    }
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41

NYOJ 746:http://acm.nyist.net/JudgeOnline/problem.php?pid=746

#include 
#include 
#include 
#include 
#define sf scanf
#define pf printf
using namespace std;
typedef long long int LL;
/*  分类 区间DP入门题
    题意: 要求将一个整数N 划分成M段 使这M段的乘积最大

    现在我们将整数 看作一个由数字组成的字符串 那么
    DP[l][r][m] 表示将子串(l - r)划分成M段的最大乘积的值
    那么DP[l][r][m] max{DP[l][k][p] * DP[k + 1][r][q]}  (k >= l ^ k < r ^ (p + q) == m)

*/

const int maxn = 20;
LL DP[maxn][maxn][maxn];
LL G[maxn],F[maxn];
int len;
char s[maxn];
LL DPS(int l,int r,int m){
    if(~DP[l][r][m]) return DP[l][r][m];
    LL& ans = DP[l][r][m] = 0;
    if(r - l + 1 < m) ans = 0;
    else if(m == 1){
        ans = G[l] / F[r + 1];
    }
    else for(int k = l;k < r;++k){
        for(int p = 1;p <= m;++p){
            for(int q = 1;q <= m;++q){
                if(p + q == m){
                    ans = max(ans,DPS(l,k,p) * DPS(k + 1,r,q));
                }
            }
        }
    }
//    pf("%d %d %d %lld\n",l,r,m,ans);
    return ans;
}


int main(){
    int T;sf("%d",&T);
    while( T-- ){
        memset(DP,-1,sizeof(DP));
        int n,m;
        sf("%s %d",s,&m);
        len = n = strlen(s);
        F[len] = 1;
        G[len] = 0;
        for(int i = len - 1;~i;--i) {
            F[i] = F[i + 1] * 10;
            G[i] = G[i + 1] + (s[i] - '0') * F[i + 1];
        }
//        pf("%lld\n",G[0] / F[1]);
        pf("%lld\n",DPS(0,n - 1,m));
    }
    return 0;
}


你可能感兴趣的:(代码)