zoukankan      html  css  js  c++  java
  • 区间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;
    }
    
  • 相关阅读:
    佛学的经典 —— 《妙色王求法偈》
    dom4j的用法
    Android真机网络adb联机调试初探
    CString的部分实现剖析
    文件下载:"Content-disposition","attachment; filename=中文名>>>解决方案
    内存块重叠的判断方法
    闭包
    Twenty Newsgroups Classification任务之二seq2sparse(5)
    IE 加速插件之 Google Chrome Frame
    [Android面试题-7] 写出一个Java的Singleton类(即单例类)
  • 原文地址:https://www.cnblogs.com/StungYep/p/12254134.html
Copyright © 2011-2022 走看看