区间dp

Posted stungyep

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了区间dp相关的知识,希望对你有一定的参考价值。

定义:区间dp就是在区间上进行动态规划,求解一段区间上的最优解。其主要思想就是现在小区间进行dp得到最优解,然后再利用小区间的最优解结合并大区间的最优解。

区间dp经典问题:

1.石子合并问题

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

样例: 3堆石子 1 2 3 输出9(1+2+1+2+3=9)

我们假设dp[i][j]表示取第 i~j 堆的最小代价;由此我们可以得出状态转移方程:(dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]+w[i][j]));得出状态转移方程后我们只需枚举k即可。(其中w[i][j]为取 i~j 石子的代价,即sum[j] - sum[i-1])。

show code(有一点要注意的,要先枚举长度,因为后面的状态的长度要用到前一个状态的长度):

#include <iostream>
#include <cstdlib>
#include <algorithm>
#include <cstring>
#include <stdio.h>

using namespace std;
const int maxn=105;
const int inf=0x3f3f3f3f;
int arr[maxn],sum[maxn];
int dp[maxn][maxn];

int main()
{
    ios::sync_with_stdio(false);

    int n,T;
    cin>>T;
    while(T--)
    {
        cin>>n;
        memset(dp,inf,sizeof(dp));
        memset(sum,0,sizeof(sum));
        sum[0]=0;
        for(int i=1;i<=n;++i){
            cin>>arr[i];
            sum[i]=sum[i-1]+arr[i];
            dp[i][i]=0;                     //不移动则无代价
        }
        for(int len=2;len<=n;++len)         //先枚举长度len
        {
            for(int i=1;i+len-1<=n;++i)
            {
                int j=i+len-1;
                for(int k=i;k+1<=j;++k)
                    dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1]);
                    //cout<<"dp["<<i<<"]["<<j<<"] is:"<<dp[i][j]<<endl;
            }
        }
        cout<<dp[1][n]<<endl;
    }

    system("pause");
    return 0;
}

2.括号匹配问题

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

分析:求出最大匹配数,用总长度-最大匹配数就是答案。

最大匹配数可以用LCP求,最长公共子序列的最大值*2就是最大匹配数,但用dp求LCP复杂度太高(其实可以用后缀树组),所以我们假装不知道后缀数组,考虑另一种方法:通过dp让它满足子结构求解;

定义dp[i][j]为串中第 i 个到第 j 个个括号的最大匹配数目;加入我们已近知道了i 到 j 的最大匹配数目,那么i+1 到 j+1 的区间也可以很简单的得到。假如第 i 个和第 j 个是一对匹配的括号那么 dp[i][j]=dp[i+1][j-1]+2(这个很重要也很难确定); 然后再更新最大值:(dp[i][j]=max(dp[i][j],dp[i][k]+dp[k+1][j]))

show code:

#include <iostream>
#include <cstdlib>
#include <algorithm>
#include <cstring>
#include <stdio.h>

using namespace std;
const int maxn=105;
char s[maxn];
int dp[maxn][maxn];

int main()
{
    ios::sync_with_stdio(false);

    while(cin>>s+1)
    {
        if(s[1]=='e')    break;
        int n=strlen(s+1);
        memset(dp,0,sizeof(dp));                //dp[i][j]为从i到j匹配到的最大匹配数
        for(int len=2;len<=n;++len)
        {
            for(int i=1;i+len-1<=n;++i)
            {
                int j=i+len-1;
                if( (s[i]=='('&&s[j]==')') || (s[i]=='['&&s[j]==']') )
                    dp[i][j]=dp[i+1][j-1]+2;
                for(int k=i;k+1<=j;++k)
                    dp[i][j]=max(dp[i][j],dp[i][k]+dp[k+1][j]);
            }
        }
        cout<<dp[1][n]<<endl;
    }

    system("pause");
    return 0;
}

3.整数划分问题

题目描述:现在给你两个数 n 和 m ,要求在数字 n 的数位中插入 m-1 个乘号,使得这个n最大。

样例:n=111, m=2→(11×1=11)输出11;n=1111, m=2→(11×11) 输出121

解题思路:设 dp[i][j] 代表从第一位到第 i 为插入 j 个乘号得到的乘积的最大值,所以我们只需要枚举放第 j 号乘号的位置即可。很容易得到状态转移方程:(dp[i][j]=max(dp[i][j],dp[i][k]*num[k+1][j]));其中
num[i][j] 代表从arr[i] 到 arr[j]这段连续区间代表的数值。

show code:

#include <iostream>
#include <cstdlib>
#include <algorithm>
#include <cstring>
#include <stdio.h>

using namespace std;
typedef long long ll;           //注意范围,乘积可能会爆Int
const int maxn=105;
char s[maxn];
ll dp[maxn][maxn],val[maxn][maxn];  //val为从i到j的数值
int m;
inline int id(char s)
{
    return s-'0';
}

int main()
{
    ios::sync_with_stdio(false);

    cin>>s+1>>m;
    int len=strlen(s+1);
    memset(dp,0,sizeof(dp));
    for(int i=1;i<=len;++i){        //求val数组
        val[i][i]=id(s[i]);
        for(int j=i+1;j<=len;++j)
            val[i][j]=val[i][j-1]*10+id(s[j]);
    }
    for(int i=1;i<=len;++i)
        dp[i][0]=val[1][i];
    for(int num=1;num<=m-1;++num)         //先枚举乘号的个数
    {
        for(int i=num+1;i<=len;++i){        //从可能最小位乘号后面一位开始枚举位数
            for(int k=num;k<i;++k)         //从可能的最小乘号那一位开始枚举
                dp[i][num]=max(dp[i][num],dp[k][num-1]*val[k+1][i]);
        }
    }
    cout<<dp[len][m-1]<<endl;

    system("pause");
    return 0;
}

以上是关于区间dp的主要内容,如果未能解决你的问题,请参考以下文章

HDU 5115 Dire Wolf ——(区间DP)

HDU 5115 Dire Wolf(区间dp)

区间DP小结(附经典例题) 转载

poj2955 区间dp

BZOJ1260 [CQOI2007]涂色paint(区间dp)

括号匹配问题(区间dp)