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

    先在小区间DP得到最优解,再合并小区间求大区间最优解,一般把左右两个相邻的子区间合并,需要从小到大枚举所有可能的区间。

    https://vjudge.net/problem/51Nod-1021

    先举个栗子:

    有一二三堆石子,这个时候第一步有两种合并的方法,分别是1和2合并,2和3合并,那么最后应该选择两者中较小的那一个方案,也就是:

    min(dp[1][1]+dp[2][3],dp[1][2]+dp[3][3])

    这里的dp[2][3]指的是第二堆石子与第三堆石子合并,把整个石子的排列看作是一个大区间的话,2到3石子就是其中的一个小区间,不断将小区间合并,最后得到大区间的最小值,那么三堆石子的最小值就应该是:

    dp[1][3]=min(dp[1][1]+dp[2][3],dp[1][2]+dp[3][3])+sum[1][3](这里的sum是从1到3的和)

    那么问题的求解就是需要求得dp[1][n]的最小值,由三堆石子推广得到的状态转移方程为:

    dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j])+sum[i][j-i+1](这里也可以是sum[j]-sum[i-1])

    具体代码:

    #include<bits/stdc++.h>
    using namespace std;
    const int INF=1<<30;//取一个无穷大
    const int N=300;
    int sum[N],n;
    

    int Minval(){
    int dp[N][N];
    for(int i=1;i<=n;i++)
    dp[i][i]
    =0;
    for(int len=1;len<=n;len++){//len是i到j的距离
    for(int i=1;i<=n-len;i++){
    int j=i+len;
    dp[i][j]
    =INF;
    for(int k=i;k<j;k++)//i和j之间用k来分割,就像是栗子中三堆石子里的第二堆
    dp[i][j]
    =min(dp[i][j],dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1]);
    }
    }
    return dp[1][n];
    }

    int main()
    {
    while(~scanf("%d",&n)){
    sum[
    0]=0;
    for(int i=1;i<=n;i++){
    int x;
    scanf(
    "%d",&x);
    sum[i]
    =sum[i-1]+x;
    }
    printf(
    "%d ",Minval());
    }
    return 0;
    }

     那么,变个形:
    http://120.78.128.11/Problem.jsp?pid=2385

    这道题与上面那一道的区别是石子不再是排列成一排,而是被摆放为了一个环,那么就有一个头尾相接可合并的变化,如果再次看作一排的话,那么这一排的头和尾就是不确定的,有可能是从1到n,也有可能是从2到n再到1,3到n再到2,按照这样的规律,我们可以让1到n之后再加上一排1到n,这样就能把环中头尾相接的情况考虑到,剩下的步骤就和1到n的链状DP相同了。

    放代码:

    #include<bits/stdc++.h>
    using namespace std;
    

    #define INF 0x3f3f3f
    int sum[305],x[305];
    int dp[605][605];
    int n;

    int main()
    {
    scanf(
    "%d",&n);
    memset(sum,
    0,sizeof(sum));
    memset(dp,INF,
    sizeof(dp));
    for(int i=1;i<=n;i++){
    scanf(
    "%d",&x[i]);
    sum[i]
    =sum[i-1]+x[i];
    dp[i][i]
    =0;
    }
    for(int i=1;i<=n;i++){
    sum[i
    +n]=sum[i+n-1]+x[i];//这里相当于再加上了一排1到n,需要把sum和dp重新赋值
    dp[i
    +n][i+n]=0;
    }
    for(int len=1;len<=n;len++){//这一段同上
    for(int i=1;i+len<=2*n;i++){
    int j=i+len-1;
    for(int k=i;k<j;k++)
    dp[i][j]
    =min(dp[i][j],dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1]);
    }
    }
    int f=0xfffffff;
    for(int i=1;i<=n;i++){
    f
    =min(f,dp[i][i+n-1]);//在所有情况中找到最小的值
    }
    printf(
    "%d ",f);
    return 0;
    }

    那么到这里就差不多了,再回头看看代码,发现在寻找最优分割点k时有重复的步骤,可以将找到的最优分割点k保存下来用于下一次循环,这样就能避免重复计算,从而降低复杂度,拿第一道题举栗子:

    用s[i][j]表示区间[i,j]中的最优分割点,那么代码就应该是这样的:

    int Minval(){
        int dp[N][N],s[N][N];
        for(int i=1;i<=n;i++){
            dp[i][i]=0;
            s[i][i]=i;
        }
        for(int len=1;len<n;len++){
            for(int i=1;i<=n-len;i++){
                int j=i+len;
                dp[i][j]=INF;
                for(int k=s[i][j-1];k<=s[i+1][j];k++){
                    if(dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1]<dp[i][j]){
                        dp[i][j]=dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1];
                        s[i][j]=k;
                    }
                }
            }
        }
        return dp[1][n];
    }

    EOF

  • 相关阅读:
    使用SVG symbols建立图标系统完整指南
    ural 1874 Football Goal
    ural 1572 Yekaterinozavodsk Great Well
    ural 1084 Goat in the Garden
    ural 1192 Ball in a Dream
    ural 1020 Rope
    ural 1494 Monobilliards
    ural 1671 Anansi's Cobweb
    ural 1613 For Fans of Statistics
    ural 1126 Magnetic Storms
  • 原文地址:https://www.cnblogs.com/Untergehen/p/14342335.html
Copyright © 2011-2022 走看看