• 洛谷


    第一次接触区间DP,真奇妙.....

     1 #include <iostream>
     2 #define Maxsize 202
     3 #define INF 0x3fffffff
     4 using namespace std;
     5 int dp[Maxsize][Maxsize];
     6 int dp2[Maxsize][Maxsize];
     7 int arr[Maxsize];
     8 int sum[Maxsize][Maxsize];
     9 int get_sum(int i,int j){
    10     if(sum[i][j] == 0){
    11         for(int t = i; t <= j; t++)sum[i][j] += arr[t];
    12     }
    13     return sum[i][j];
    14 }
    15 int main(){
    16     int num;
    17     cin >> num;
    18     for(int i = 1,j = num + 1; i <= num; i++,j++){
    19         cin >> arr[i];     arr[j] = arr[i];
    20     }
    21     
    22     for(int length = 2; length <= num; length++){   // 枚举长度, 1 不用枚举了, 如果枚举长度为 1 , 找不到分界点 ( end = start + 1 - 1 = start; k = start = end )
    23         for(int start = 1; start <= num; start++){  // 枚举起点
    24             int end = start + length - 1;           // 确定终点
    25             dp2[start][end] = INF;
    26             for(int k = start; k < end; k++){       // 确定分界点 , k 一定要从 start , 否则对于 dp[k+1][end] 这种情况会漏掉第二个石子分向右边的情况 .
    27                 dp[start][end] = max(dp[start][end],dp[start][k] + dp[k+1][end] + get_sum(start,end));
    28                 dp2[start][end] = min(dp2[start][end],dp2[start][k] + dp2[k+1][end] + get_sum(start,end));
    29             }
    30         }
    31     }
    32     
    33     int ans = 0;
    34     int ans2 = INF;
    35     for(int i = 1; i <= num; i++){
    36         ans = max(ans,dp[i][i+num-1]);
    37         ans2 = min(ans2,dp[i][i+num-1]);
    38     }
    39     cout<<ans2<<" "<<ans;
    40     return 0;
    41 }

    主要谈一谈为什么这样遍历可以保证递推顺序正确:

    由于以区间长度为主循环,以起点为次循环,故第一遍循环可以确定两堆石子合并的所有可能情况。这个很好理解。

    第二遍循环,是在求解当区间长度为3时的所有情况。这个问题的求解需要利用区间长度为2的子问题的结果,而子问题再上一次被求解过了。

    以 length = 3 , start = 1, end = 3, k = 2 为例: 

    求解这个状态的方程为: dp [1] [3] = max(dp[1] [3] ,  dp[1] [2] + dp[3] [3] + get_sum(1,3) ) ; 

    可以看到, dp[1] [2] 已经在 length = 1 时求解过了, 而dp[3] [3] 是 i = j ( length = 0 ) 的默认情况,为0;

    同理我们看 length = 3 , start = 2, end = 4, k = 3 :

    求解这个状态的方程为 : dp[2] [4] = max(dp[2] [4],  dp[2] [3] + dp[4][4] + get_sum(1,3) ) ;

    总结规律可以看出当我们枚举分界点k的时候,实际上就是把问题分段了,子问题的length 当然小于当前问题的 length,因此这个递推顺序是很正确的。

    另外,当我们是在无法弄明白如何确定边界状态、递推顺序的时候,用记忆化递归也比较好。

    #include <iostream>
    #define Maxsize 400
    #define INF 10000
    using namespace std;
    int arr[Maxsize];
    int DP[Maxsize][Maxsize];
    int DP2[Maxsize][Maxsize];
    int sum[Maxsize][Maxsize];
    int get_sum(int i,int j){
        if(sum[i][j] == 0){
            int z = 0;
            for(int p = i; p <= j; p++)z += arr[p];
            sum[i][j] = z;
        }
        return sum[i][j];
    }
    int dfs(int i,int j){
        if(i == j)return 0;
        else if(DP[i][j] == 0){
            for(int k = i ; k < j; k++){
                DP[i][j] = max(DP[i][j],dfs(i,k)+dfs(k+1,j) + get_sum(i, j));
            }
        }
        return DP[i][j];
    }
    int dfs2(int i,int j){
        if(i == j)return 0;
        else if(DP2[i][j])return DP2[i][j];
        else{
            int m = INF;
            for(int k = i; k < j; k++)
                m = min(m,dfs2(i, k)+dfs2(k+1, j)+get_sum(i, j));
            DP2[i][j] = m;
        }
        
        return DP2[i][j];
    }
    
    int main(){
        int num;
        cin >> num;
        for(int i = 1,j = num + 1; i <= num; i++,j++){
            cin >> arr[i]; arr[j] = arr[i];
        }
        int ans = 0,ans2= INF;
        for(int i = 1,j = num; i < num; i++,j++){
            ans = max(ans,dfs(i,j));
            ans2 = min(ans2,dfs2(i,j));
        }
        cout<<ans2<<endl<<ans;
        return 0;
    }
    ---- suffer now and live the rest of your life as a champion ----
  • 相关阅读:
    前序中序输出后序
    Blah数集
    中缀表达式转后缀表达式 (栈)
    1357:车厢调度 (栈)
    最长公共上升子序列 (LIS+LCS+记录)
    1481:Maximum sum (前缀和+dp)
    8464:股票买卖
    7627:鸡蛋的硬度
    2989:糖果
    U33405 纽约 (二分)
  • 原文地址:https://www.cnblogs.com/popodynasty/p/12133870.html
Copyright © 2020-2023  润新知