zoukankan      html  css  js  c++  java
  • HDU 1024 Max Sum Plus Plus【动态规划求最大M子段和详解 】

    Max Sum Plus Plus

    Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)
    Total Submission(s): 29942    Accepted Submission(s): 10516


    Problem Description
    Now I think you have got an AC in Ignatius.L's "Max Sum" problem. To be a brave ACMer, we always challenge ourselves to more difficult problems. Now you are faced with a more difficult problem.

    Given a consecutive number sequence S1, S2, S3, S4 ... Sx, ... Sn (1 ≤ x ≤ n ≤ 1,000,000, -32768 ≤ Sx ≤ 32767). We define a function sum(i, j) = Si + ... + Sj (1 ≤ i ≤ j ≤ n).

    Now given an integer m (m > 0), your task is to find m pairs of i and j which make sum(i1, j1) + sum(i2, j2) + sum(i3, j3) + ... + sum(im, jm) maximal (ix ≤ iy ≤ jx or ix ≤ jy ≤ jx is not allowed).

    But I`m lazy, I don't want to write a special-judge module, so you don't have to output m pairs of i and j, just output the maximal summation of sum(ix, jx)(1 ≤ x ≤ m) instead. ^_^
     
    Input
    Each test case will begin with two integers m and n, followed by n integers S1, S2, S3 ... Sn.
    Process to the end of file.
     
    Output
    Output the maximal summation described above in one line.
     
    Sample Input
    1 3 1 2 3
    2 6 -1 4 -2 3 -2 3
    Sample Output
    6
    8
    Hint
    Huge input, scanf and dynamic programming is recommended.
     
    Author
    JGShining(极光炫影)

    【问题描述】----最大M子段和问题
    给定由 n个整数(可能为负整数)组成的序列a1,a2,a3,……,an,以及一个正整数 m,要求确定序列 a1,a2,a3,……,an的 m个不相交子段,
    使这m个子段的总和达到最大,求出最大和。

    题解:转自http://www.cnblogs.com/peng-come-on/archive/2012/01/15/2322715.html
    动态规划的思想。
    1.基本思路:
      首先,定义数组num[n],dp[m][n].
      num[n]用来存储n个整数组成的序列.
      dp[i][j]用来表示由前 j项得到的含i个字段的最大值,且最后一个字段以num[j]项结尾。仔细想想,我们可以知道:
      dp[i][j]=max(dp[i][j-1]+num[j],dp(i-1,t)+num[j])   其中i-1<=t<=j-1.
      (因为必须是以 num[j] 结尾的,所以num[j]一定属于最后一个子段,即要么自己独立成一个子段,要么与前边以num[j-1]结尾的子段联合)
      所求的最后结果为 max( dp[m][j] ) 其中1<=j<=n.
      但是,我们会发现,当n非常大时,这个算法的时间复杂度和空间复杂度是非常高的,时间复杂度近似为O(m*n^2),
      空间复杂度近似为O(m*n).因此,我们需要优化算法来降低时间复杂度和空间复杂度.
    2.优化算法:
      (1)节省时间
      由基本思路,我们可以知道,dp[i][j]=max(dp[i][j-1]+num[j],dp(i-1,t)+num[j]),其中i-1<=t<=j-1.我们只要找到dp[i][j-1]
      和dp[i-1][t]的最大值加上num[j]即为dp[i][j].所以,定义一个数组pre_max[n],用pre_max[j-1]来表示求解dp[i][j]时dp[i-1][t]
      的最大值,则dp[i][j]=max(pre_max[j-1],dp[i][j-1])+num[j].
      特别注意,pre_max[n]这个位置的存储空间是始终用不到的,因此可以用来存储其他数值,在接下来会用到。
      在求解dp[i][j]的同时,我们可以计算出dp[i][t];i<=t<=j的最大值,这个最大值在计算dp[i+1][j+1]的时候需要作为pre_max[j]的
      形式被使用,我们先把它存在pre_max[n]中。
      你可能会问:为什么不把它直接放在pre_max[j]中呢?因为你接下来需要计算dp[i][j+1]的值,需要用到pre_max[j]中原来的值,
      如果你把它存在这里,就会覆盖掉计算dp[i][j+1]所需要的那个值。所以,先把它放在pre_max[n]中。
      当我们计算完dp[i][j+1]之后,就会发现pre_max[j]中的值已经没有用处了,我们可以把它更新为计算dp[i+1][j+1]所需要的那个值,
      即之前放在pre_max[n]中的那个值,即执行pre_max[j]=pre_max[n].
      这样我们就节省了计算最大值时付出的时间代价。
      (2)节省空间
      通过时间的节省,我们突然间发现程序执行结束后pre_max[n]的值即为最后的结果,pre_max[n]数组才是我们希望求解的,
      dp[m][n]这个庞大的数组已经不是那么重要了,因此,我们现在用整型数tmp来代替dp[m][n],用来临时存储dp[i][j]的值,
      作为求解pre_max[n]的中介。
      这样就节省了dp[i][j]占用的极大的空间.

    代码一:

     1 #include <cstdio>
     2 #include <iostream>
     3 const int MAX = 1000005;
     4 
     5 using namespace std;
     6 
     7 int num[MAX], pre_max[MAX]; 
     8 
     9 inline int max(int a, int b)
    10 {
    11     return a > b ? a : b;
    12 }
    13 
    14 int DP(int n, int m)
    15 {
    16     for(int i = 1; i <= m; ++i)
    17     {
    18         /*****初始化*****/ 
    19         int tmp = 0;
    20         for(int k = 1; k <= i; ++k)
    21             tmp += num[k];
    22         pre_max[n] = tmp;
    23         
    24         for(int j = i+1; j <= n; ++j)
    25         {
    26             tmp = max(pre_max[j-1], tmp) + num[j];
    27             pre_max[j-1] = pre_max[n];
    28             pre_max[n] = max(pre_max[n], tmp);         
    29         }
    30     }
    31     return pre_max[n];
    32 }
    33 
    34 int main()
    35 {
    36     int n, m;
    37     while(~scanf("%d%d", &m, &n))
    38     {
    39         for(int i = 1; i <= n; ++i)
    40         {
    41             scanf("%d", &num[i]);
    42             pre_max[i] = 0;   
    43         }
    44         printf("%d
    ", DP(n, m));
    45     }
    46     return 0;
    47 }

     代码二:(讨论区粘的)

     1 #include<iostream>
     2 #include<climits>
     3 #include<cstring>
     4 #include<cstdio>
     5 #include<cstdlib>
     6 using namespace std;
     7 int max(int *a,int m,int n)
     8 {
     9     int *c;
    10     int *p;
    11     int max, i, j;
    12     c=new int[n+1];
    13     p=new int[n+1];
    14     for(i=0; i<n+1; i++)
    15         p[i]=0;
    16     c[0]=0;
    17     for(i=1; i<=m; ++i)
    18     {
    19         max=INT_MIN;
    20         for(j = i; j <= n; ++j)
    21         {
    22             if(c[j-1]< p[j-1])
    23                 c[j]= p[j-1]+a[j-1];
    24             else
    25                 c[j]=c[j-1]+a[j-1];
    26             p[j-1]=max;
    27             if(max<c[j])
    28                 max=c[j];
    29         }
    30         p[j-1]=max;
    31     }
    32     delete []p;
    33     delete []c;
    34     return max;
    35 }
    36 int main()
    37 {
    38     int n,m,i,*d;
    39     while(cin>>m>>n)
    40     {
    41         d=new int[n];
    42         for(i=0;i<n;++i)
    43             cin>>d[i];
    44         cout<<max(d, m, n)<<endl;
    45         delete [] d;
    46     }
    47     return 0;
    48 }
    思路:
    dp[i][j]表示前j个元素分成i段的最优解,同时这个最优解是由a[j]元素结束的。
    转移方程是dp[i][j]=max{f[i][j-1]+a[j],f[i-1][k]+a[j],(i-1<=k<j)} (i<=j<=n-m+i)
    其中j的上下界的确定比较麻烦。现在分别解释上界和下界:
    上界:dp[i][j]中,如果j=i-1,意思就是在前面i-1个元素中分成i段,这个是不可能实现的。
    下界:如果m=n=4,这时dp[2][4]求出来了,意思是前面的四个元素分成了两段,当是还有两段要分,
          所以求出这个是没有意义的。当然求出来也不会影响结果,只是这样时间复杂度就提高了。
    这是其中一个特例的状态转移表 m=4,n=6 ,-1 4 -2 3 -2 3
    hdu <wbr>1024 <wbr>最大M子段和
    没有填的说明不用算。
    很显然dp[i][i]=dp[i-1][i-1]+a[i],
    所以对角线上面有:
    hdu <wbr>1024 <wbr>最大M子段和
    现在演示一下转移过程。如何求下图的框中的元素的值
    hdu <wbr>1024 <wbr>最大M子段和
    由下面涂色的元素的最大值加上a[3]=-2求的如下图
    hdu <wbr>1024 <wbr>最大M子段和

    最大的是4,所以4+(-2)=2;框中填2;
    假如框中的元素是dp[i][j],画圈的元素表示的是,左边那个是dp[i][j-1],上面的几个是dp[i-1][k](i-1<=k<j)} ,这个就是上面的转移方程的表格表示法。
     
     
    其他细节如果理解上面的内容,就可以优化了
     1 #include<stdio.h>
     2 __int64 dp[2][1000001];
     3 __int64 a[1000001];
     4 __int64 b[1000001];
     5 __int64 res;
     6 int n,m;
     7 __int64 Max(__int64 x,__int64 y)
     8 {
     9     if(x>y)return x;
    10     else   return y;
    11 }
    12 int main()
    13 {
    14    
    15 //    freopen("a.txt","r",stdin);
    16     while(scanf("%d%d",&m,&n)!=EOF)
    17     {
    18         int t=1;
    19         res=0;
    20         int i,j,k;
    21         for(i=1;i<=n;i++){b[i]=0;dp[0][i]=dp[1][i]=0;scanf("%I64d",&a[i]);}
    22         for(i=1;i<=m;i++)
    23         {
    24             dp[t][i]=dp[1-t][i-1]+a[i];
    25             __int64 max=dp[1-t][i-1];
    26             for(j=i+1;j<=n-m+i;j++)
    27             {
    28                 max=Max(max,dp[1-t][j-1]);
    29                 dp[t][j]=Max(dp[t][j-1],max)+a[j];
    30             }
    31             t=1-t;
    32         }
    33         t=1-t;
    34         res=-1111111111111;
    35         for(j=m;j<=n;j++)if(res<dp[t][j])res=dp[t][j];
    36         printf("%I64d
    ",res);
    37     }
    38 
    39     return 0;
    40 }

    下面给出详解代码:

      1     #include<iostream>  
      2     #include<cstdio>  
      3     using namespace std;  
      4     const int MAX=1000001;  
      5     int dp[2][MAX];  
      6     int w[MAX];  
      7     int sum[MAX];//不做不知道,一做吓一跳,原来在主函数里开个sum[MAX],是不行的,因为MAX是在太大!  
      8       
      9     /*这是我的老师贴出的提示!现在才理解到内涵! 
     10      
     11     VC定义数组时请注意大小!定义时,局部数组大小<=1MB,全局数组<=2GB,定义时如果超过这个限制将会出现如"segment error"之类的错误.以下的程序可以帮助你证明这一点. 
     12      
     13     以下程序数组如果再大点,运行出错,说明局部变量分配内存<=1MB 
     14     #include<stdio.h> 
     15     int main() 
     16     { 
     17     int a[1024*1024/4-4000]; 
     18     int i; 
     19     for(i=0;i<1024*1024/4-4000;i++) 
     20     { 
     21     a[i]=i; 
     22     printf("%d
    ",a[i]); 
     23     } 
     24     return 0; 
     25     }  
     26      
     27     以下程序数组如果再大点,运行出错,说明全局变量分配内存<=2GB 
     28     #include<stdio.h> 
     29     int a[1024*1024*470]; 
     30     int main() 
     31     { 
     32     long int i; 
     33     for(i=0;i<1024*1024*470;i++) 
     34     { 
     35     a[i]=i; 
     36     printf("%d
    ",a[i]); 
     37     } 
     38     return 0; 
     39     } 
     40      
     41      
     42     内存的三种分配方式:静态存储区分配,栈上分配,堆上分配。 全局数组是在静态存储区分配,而局部数组是在栈上分配,所以大小受到的限制不一样. 
     43     */  
     44       
     45     int cmax(int a,int b)//求最大值  
     46     {  
     47         return a>b?a:b;  
     48     }  
     49       
     50     int main()  
     51     {  
     52         int i,k;  
     53         int m,n;  
     54           
     55         while(scanf("%d%d",&m,&n)>0)  
     56         {  
     57             sum[0]=0;  
     58             for(i=1;i<=n;i++)  
     59             {  
     60                 cin>>k;  
     61                 sum[i]=sum[i-1]+k;//sum[i]里存的是前i个元素的和  
     62                 dp[0][i]=0;//从前i个元素中取0段,最大值为0  
     63             }  
     64     //我们假设a[i]中存放该序列第i个值,w[i][k]表示前k个数分为i段,第k个数必须选这种情况下取得的最大值  
     65     //b[i][k]表示在前k个数中取i段这种情况下取得的最大值  
     66       
     67     //w[i][k]:前k个数分为i段,第k个数必须选;1:第k个数单独为1段;2:第k个数与前面的数连一块。w[i][k]=max(b[i-1][k-1],w[i][k-1])+a[k];  
     68     //b[i][k]:前k个数分为i段,第k个数可选可不选;1:选第k个数,2:不选第k个数。b[i][k]=max(b[i][k-1],w[i][k])  
     69     //w[i][k]=max(b[i-1][k-1],w[i][k-1])+a[k];  
     70     //b[i][k]=max(b[i][k-1],w[i][k]);  
     71     //w[i][k],b[i][i]容易求得,所以由b[i-1][k-1]->w[i][k]->b[i][k],只要知道b[0][k],全部都能成功运行!  
     72       
     73     //当从k个元素中取j段,可以分为两种情况,即第k个元素可以取,也可以不取,取,那么a[k]要么是单独为一段b[i-1][k-1]+a[k];  
     74     //要么是第k个数与前面的数连一块,即w[i][k-1]+a[k],故w[i][k]=max(b[i-1][k-1],w[i][k-1])+a[k];  
     75       
     76     //要么不取 即b[i][k]=b[i][k-1];  
     77     //综合起来,b[i][k]=max(b[i][k-1],w[i][k]);  
     78            int t=1;  
     79            for(i=1;i<=m;i++)//i表示在取i段,自然i<=m;  
     80            {  
     81                  
     82                for(k=i;k<=n;k++)//为什么k从i开始?dp[i][k](k<i)是没有意义的!  
     83                {  
     84                    if(i==k)  
     85                    dp[t][k]=w[k]=sum[k];//从k个数中取k段的最大值是前k个数的和  
     86                    else  
     87                    {  
     88                        w[k]=cmax(dp[1-t][k-1],w[k-1])+sum[k]-sum[k-1];//w[k]表示k个元素取i段,a[k]必须取时的最大值  
     89             //w[i][k]=max(b[i-1][k-1],w[i][k-1])+a[k];  
     90                        dp[t][k]=cmax(dp[t][k-1],w[k]);//dp[t][k]表示在a[k]可取可不取这两种情况下取得的最大值  
     91                        //自然,dp[t][k]记录的就是在前k个元素中取i段时取得的最大值!  
     92                    }  
     93                }  
     94                t=1-t;//t在1,0之间交替变换  
     95       //为什么要交替呢?这是为了节省空间  
     96       //仔细观察递归式  
     97       //w[i][k]=max(b[i-1][k-1],w[i][k-1])+a[k];  
     98       //b[i][k]=max(b[i][k-1],w[i][k]);  
     99       //我们发现,对于取i段,w[i][j]只与b[i-1][k-1]和w[i][k-1]有关,与之前的那一些项没有关系  
    100       //因此我们数组可以开小一点,用更新来覆盖掉前面的值!  
    101            }  
    102            cout<<dp[m%2][n]<<endl;//奇次轮还是偶次轮  
    103       
    104         }  
    105         system("pause");  
    106         return 0;  
    107     }  
  • 相关阅读:
    20200226 Java IO流——廖雪峰
    20200225 Java 多线程(2)-廖雪峰
    20200225 Java 多线程(1)-廖雪峰
    20200224 尚硅谷ElasticSearch【归档】
    20200224 一 概述
    20200222 尚硅谷Dubbo【归档】
    20200222 四、dubbo原理
    Improved robustness of reinforcement learning policies upon conversion to spiking neuronal network platforms applied to Atari Breakout game
    Reinforcement learning in populations of spiking neurons
    Solving the Distal Reward Problem through Linkage of STDP and Dopamine Signaling
  • 原文地址:https://www.cnblogs.com/ECJTUACM-873284962/p/7219941.html
Copyright © 2011-2022 走看看