zoukankan      html  css  js  c++  java
  • 区间DP入门

    所为区间DP,主要是把一个大区间拆分成几个小区间,先求小区间的最优值,然后合并起来求大区间的最优值。

    区间DP最关键的就是满足最优子结构以及无后效性!!

    例如像是石子合并和括号匹配这两类比较经典的模型。

    一般的区间dp写法是:

        for(int len=2;len<=n;len++)    //枚举区间长度 
        {
            for(int i=1;i<=(n<<1)-len+1;i++)    //区间的左端点 
            {
                int j=i+len-1;
                for(int s=i;s<j;s++)
                {
                    //大区间与小区间的关系;
                }
            }
        }

    转移方程的推理:

    首先,要计算合并的最大值、最小值,既然是动态规划,我们需要洞悉其中一些关联且确定的状态。

    以下以最大值为例。

    既然是最大值,那么求得的结果是否满足每一区间都是该区间所能达得到的的最大值?

    显然是这样的。反证法:倘若有一个区间不是,那么换做该区间取得最大值的方案,最终结果将比原得分大。显然必定满足任意区间得分一定是该区间内的最大值。

    这样我们可以定义状态f[i][j],表示i到j合并后的最大得分。其中1<=i<=j<=N。

    既然这样,我们就需要将这一圈石子分割。很显然,我们需要枚举一个k,来作为这一圈石子的分割线。

    这样我们就能得到状态转移方程:

    $f[i][j] = max(f[i][k] + f[k+1][j] + d(i,j))$ 其中,1<=i<=<=k<j<=N。

    d(i,j)表示从i到j石子个数的和,也就是合并的代价。

    需要确定首尾指针,和枚举中间的断点,时间复杂度O(n3),虽然可以进一步利用什么四边形优化,在这里不做过探讨。

    对于最简单的石子合并(成链状)来说,就是一个模板题,答案自然是dp[1][n]。

    而对于呈环状的的来说则需要将数据复制一遍,答案为$max(dp[ i =(1-n) ][ i+n-1 ])$

    #include <algorithm>
    #include <iostream>
    #include <cstring>
    #include <cstdio>
    #include <vector>
    #include <queue>
    #include <map>
    using namespace std;
    #define LL long long
    #define mod int(1e9+7)
    #define wlz 1234567890
    int n,ans1,ans2;
    int a[211],sum[211],f1[211][211],f2[211][211];
    int main()
    {
        scanf("%d",&n);
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&a[i]);
            a[i+n]=a[i];
        }
        for(int i=1;i<=n*2;i++)
        {
            sum[i]=sum[i-1]+a[i];
            f1[i][i]=f2[i][i]=0;
        }
        for(int len=2;len<=n;len++) 
        {
            for(int i=1;i<=(n<<1)-len+1;i++) 
            {
                int j=i+len-1;
                f1[i][j]=wlz;
                for(int s=i;s<j;s++)
                {
                    f1[i][j]=min(f1[i][j],f1[i][s]+f1[s+1][j]);
                    f2[i][j]=max(f2[i][j],f2[i][s]+f2[s+1][j]);
                }
                f1[i][j]+=(sum[j]-sum[i-1]);
                f2[i][j]+=(sum[j]-sum[i-1]);
            }
        }
        ans1=wlz;
        for(int i=1;i<=n;i++)
        {
            ans1=min(ans1,f1[i][i+n-1]);
            ans2=max(ans2,f2[i][i+n-1]);
        }
        printf("%d
    %d",ans1,ans2);
    }
    放上环状代码

    对于括号匹配这一类。

    给一个括号组成的字符串,问最多能匹配多少个括号 

    我们可以把[i,j]区间的字符当成由[i+1,j]在前面加个字符或[i,j-1]在后面加一个字符得来的

    这里我们只考虑[i,j]由[i+1,j]在前面加一个字符的情况 
    如果a[i+1]到a[j]没有和a[i]匹配的,那么dp[i][j] = dp[i+1][j] 
    如果a[k]和a[i]匹配(i < k <= j),那么dp[i][j] = max(dp[i][j], dp[i+1][k-1] + dp[k+1][j] + 2); 
    比如:[xxxxx]yyyyy通过括号分成两个子串

    第二种模型就是根据匹配信息把区间划分成[i+1,k-1]和[k+1,j]

    while (gets(a+1))
    {
        if(a[1] == 'e') break;
        memset(dp, 0, sizeof(dp));
        int n = strlen(a+1);
        for (int len = 2; len <= n; len++)
        {
            for(int i = 1, j = len; j <= n; i++, j++)
            {
                dp[i][j] = dp[i+1][j];
                for (int k = i; k <= j; k++)
                    if((a[i]=='('&&a[k]==')') || (a[i]=='['&&a[k]==']'))
                        dp[i][j] = max(dp[i][j], dp[i+1][k-1] + dp[k+1][j] + 2);
            }
        }
        printf("%d
    ",dp[1][n]);
    }
    代码还可以这样写

    还有一类只需要枚举左右边界,比较简单,就不展开讲了。

  • 相关阅读:
    Git合并开发代码分支到测试代码分支
    用webdriver+phantomjs实现无浏览器的自动化过程
    软件测试工作中涉及的Linux命令整理
    Windows系统端口占用情况检查脚本
    PowerShell调用jira rest api实现对个人提交bug数的统计
    地下城堡游戏小脚本儿——自动炼金
    Java中通过JDBC远程连接Oracle数据库
    PowerShell调用jira rest api实现jira统计自动化
    【Spring】12、Spring Security 四种使用方式
    【hibernate】1、Hibernate的一个注解 @Transient
  • 原文地址:https://www.cnblogs.com/rmy020718/p/9515963.html
Copyright © 2011-2022 走看看