码迷,mamicode.com
首页 > 其他好文 > 详细

区间dp

时间:2020-02-02 23:16:36      阅读:91      评论:0      收藏:0      [点我收藏+]

标签:with   现在   line   out   长度   return   子序列   mem   iostream   

定义:区间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

标签:with   现在   line   out   长度   return   子序列   mem   iostream   

原文地址:https://www.cnblogs.com/StungYep/p/12254134.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!