zoukankan      html  css  js  c++  java
  • Week12 作业 C

    题目描述:

    给定一个具有N个数的序列,求M个不相交连续子序列的最大和,相当于是最大子段和的升级版。

    思路:

    首先定义状态:

    • 考虑第j个元素如何选择呢?很显然,第j个元素可以和第j-1个元素并在同一个段上,也可以自己单独作为一个段的开头
    • 定义f[i][j]表示考虑前j个元素,分成i段能取到的最大值,a[j]必选(第i段以a[j]为结尾)
    • 状态转移:f[i][j]=max{ f[i][j-1]+a[j], max{ f[i-1][k]+a[j],k=i-1,i,i+1,...,j-1 } },前者是把a[j]直接并在a[j-1]所在段的后面,后者是把a[j]单独开一个段
    • 边界条件:i<=0,返回0;j<=0,返回0
    • 答案:max{ f[M][K],K=M,M+1,M+2,...,N }(因为分成M段至少要M个元素)

    记忆化搜索+二维数组的直接实现(容易理解)

     1 int dp(int i, int j)
     2 {
     3     if (i <= 0 || j <= 0) return 0;
     4     if (vis[i][j]) return f[i][j];
     5     vis[i][j] = 1;
     6     f[i][j] = dp(i,j-1) + a[j];
     7     for (int k = i - 1; k <= j - 1; k++)
     8         f[i][j] = max(f[i][j], dp(i - 1, k) + a[j]);
     9     return f[i][j];
    10 }
    11 //输出结果
    12 int ans = -INT_MAX;
    13 for (int i = M; i <= N; i++)
    14     ans = max(ans, dp(M,i));
    15 cout << ans << endl;
    View Code

    不难发现,上述的时间复杂度是O(M*N*N),空间复杂度是O(M*N),不满足题目要求,应该进一步探索

    考虑状态转移中的后者,即f[i-1][k]是不受当前状态f[i][j]的影响的,即f[i-1][k]可以在f[i][j]之前计算,那么我们就可以把f[i-1][k]的最大值在计算的时候记录下来,等到f[i][j]用的时候直接拿来,不用再去搜索,但是这样就不能用递归了,因为计算顺序有了要求

    定义新数组maxF[i][j],表示考虑前j个元素,分成i段能得到的最大值,a[j]未必选(第i段未必以a[j]结尾)

    则maxF[i][j]=max{ f[i][k],k=i,i+1,i+2,...,j },边界条件:i<=0或j<=0时,返回0,其他位置初始化为 -INF

    新的F的状态转移:f[i][j]=max{ f[i][j-1]+a[j] , maxF[i-1][j-1]+a[j] }

    但是随之而来的新问题是:当f[i][j]更新时,f[i][j]可以用来更新maxF[i][j],maxF[i][j+1],maxF[i][j+2].....,这样时间复杂度又变成了O(M*N*N),因为我们不过是从原来的向前搜求最值变成了向后搜更新最大值

    以下是代码,从下述代码中,其实我们可以分析优化maxF

    递推+maxF+二维数组

     1 for (int i = 1; i <= MAXN - 1; i++)
     2     for (int j = 1; j <= MAXN - 1; j++)
     3     maxF[i][j] = -INT_MAX;
     4 for (int i = 0; i <= MAXN - 1; i++)
     5     maxF[i][0] = 0, maxF[0][i] = 0;
     6 for (int i = 1; i <= M; i++)    //M段
     7     for (int j = i; j <= N; j++)
     8     {
     9         f[i][j] = max(f[i][j - 1] + a[j], maxF[i - 1][j - 1] + a[j]);
    10         for (int k = j; k <= N; k++)
    11             maxF[i][k] = max(maxF[i][k], f[i][j]);
    12     }
    13 cout << maxF[M][N] << endl;
    View Code

    降维maxF

    必须延时更新,否则会覆盖需要用到的结果,而且正序枚举j

     降维maxF代码

     1 //初始化maxF[j]=0,因为把前j个元素分成0段,很显然最大值是0
     2 memset(maxF, 0, sizeof(maxF));
     3 for (int i = 1; i <= M; i++)    //M段
     4 {
     5     int nowMax = -INT_MAX;
     6     for (int j = i; j <=N; j++)
     7     {
     8         f[i][j] = max(f[i][j - 1] + a[j], maxF[j - 1] + a[j]);
     9         maxF[j - 1] = nowMax;    //用上一轮的最大值更新上一轮应该更新的maxF[j]
    10         nowMax = max(maxF[j - 1], f[i][j]);
    11     }
    12     maxF[N] = nowMax;    //当前i,没有下一轮j了,不用延时更新,直接更新
    13 }
    14 cout << maxF[N] << endl;
    View Code

    降维f

    考虑最初的状态转移方程中的前者,即f[i][j-1],发现i就是当前的i,所以第i轮的f数组f[i]和第i-1轮的数组f[i-1]是没有直接更新的联系的,所以可以直接用一维的f

    最终代码

     1 #define _CRT_SECURE_NO_WARNINGS 1
     2 #include <cstdio>
     3 #include <iostream>
     4 #include <algorithm>
     5 using namespace std;
     6 const int MAXN = 1e6 + 5;
     7 int f[MAXN], maxF[MAXN], a[MAXN];
     8 
     9 int main()
    10 {
    11     int N, M;
    12     while (scanf("%d %d", &M, &N) == 2)
    13     {
    14         memset(f, 0, sizeof(f));
    15         
    16         for (int i = 1; i <= N; i++)
    17             scanf("%d", a + i);
    18         //初始化maxF[j]=0,因为把前j个元素分成0段,很显然最大值是0
    19         memset(maxF, 0, sizeof(maxF));
    20         for (int i = 1; i <= M; i++)    //M段
    21         {
    22             int nowMax = -INT_MAX;
    23             for (int j = i; j <=N; j++)
    24             {
    25                 f[j] = max(f[j - 1] + a[j], maxF[j - 1] + a[j]);
    26                 maxF[j - 1] = nowMax;    //用上一轮的最大值更新上一轮应该更新的maxF[j]
    27                 nowMax = max(maxF[j - 1],f[j]);
    28             }
    29             maxF[N] = nowMax;    //当前i,没有下一轮j了,不用延时更新,直接更新
    30         }
    31         cout << maxF[N] << endl;
    32     }
    33     return 0;
    34 }
    View Code

    总结

    • 感想:动态规划是真的难,这题想了快三天,不过最后貌似搞懂了,但是我感觉,还有更容易理解的思路过程,至少现在明白这题这样做的原理了,还要继续加油,真的太菜了
    • 其他:递推其实很难写,可以定义好状态转移之后,先写记忆化搜索理解过程,再进一步变成递推;如果更新数组时,用到的以前的状态不多,可以考虑降维(滚动数组),有时候,题目的数据范围限定了必须使用一维数组
  • 相关阅读:
    bzoj2101:[USACO2010 DEC]TREASURE CHEST 藏宝箱
    P3976 [TJOI2015]旅游(未完成)
    洛谷 P 5 3 0 4 [GXOI/GZOI2019]旅行者
    NOIP原题 斗地主(20190804)
    P2860 [USACO06JAN]冗余路径Redundant Paths
    vue中的插槽(slot)
    vue动态绑定class
    发现一个ps抠毛发简单快捷高质量的方法
    propsData传递数据
    sort排序原理
  • 原文地址:https://www.cnblogs.com/qingoba/p/12994214.html
Copyright © 2011-2022 走看看