• HDU-1003:Max Sum(优化)


    Max Sum

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

    Problem Description
    Given a sequence a[1],a[2],a[3]......a[n], your job is to calculate the max sum of a sub-sequence. For example, given (6,-1,5,4,-7), the max sum in this sequence is 6 + (-1) + 5 + 4 = 14.
     

    Input

    The first line of the input contains an integer T(1<=T<=20) which means the number of test cases. Then T lines follow, each line starts with a number N(1<=N<=100000), then N integers followed(all the integers are between -1000 and 1000).
     

    Output

    For each test case, you should output two lines. The first line is "Case #:", # means the number of the test case. The second line contains three integers, the Max Sum in the sequence, the start position of the sub-sequence, the end position of the sub-sequence. If there are more than one result, output the first one. Output a blank line between two cases.
     

    Sample Input

    2
    5 6 -1 5 4 -7
    7 0 6 -1 1 -6 7 -5
     

    Sample Output

    Case 1:
    14 1 4
    Case 2:
    7 1 6
     
    概译:求一个数列最大连续和,即找到 1 ≤ i ≤ j ≤ n,使得a[ i ]+a[ i+1 ]+……+a[ j ]尽可能大,如有多组相同结果取最靠前的。
    输入:测试组数;每行第一个数为n,然后输入a[ 1 ]~a[ n ]。
    输出:Case %d: 最大连续和 连续和的起始下标i 连续和的末尾下标j
    思路:题是水题,这里我们探究一下时间和空间复杂度的优化。
    1.枚举
    ans=a[1];
    for(int i=1;i<=n;i++)
        for(int j=i;j<=n;j++)
        {
            //i和j是起点和终点 
            int sum=0;
            for(int k=i;k<=j;k++)    sum+=a[i];
            if(ans<sum)    
                ans=sum;
        }

    这大概是我们初学编程时的做法。

    2.递推前缀和

    sum[0]=0;
    for(int i=1;i<=n;i++)
        sum[i]=sum[i-1]+a[i];
    for(int i=1;i<=n;i++)
        for(int j=i;j<=n;j++)
        {
            ans=max(ans,sum[j]-sum[i-1]);
        }

    这样子就把一个区间的操作转化为了两个区间端点的操作,使得复杂度降到了O(n²)。然而面对1e5的数据量我们是没有勇气就这么提交的……

    3.分治算法(O(nlogn))

    以下借鉴刘汝佳《算法竞赛入门经典》中的思路。

    ①划分问题:把问题的实例划分成子问题;②递归求解:递归解决子问题;③合并问题:合并子问题的解得到原问题的解。

    对于区间 [ l , r ],区间中点m,所求ans = max { l~m的ans,m+1~r的ans,由m连接的、占用了部分l~m和部分m+1~r的连续和 }。

    我是用的map来记录的始末端点,详细请参见代码。哪里需要改正或可以精简之处万望指出。

     1 //140ms 
     2 #include<cstdio>
     3 #include<iostream>
     4 #include<algorithm>
     5 #include<map>
     6 #define maxn 100005
     7 #define inf 0x7fffffff
     8 using namespace std;
     9 
    10 typedef pair<int,int> P;
    11 int n,a[maxn];
    12 map<P,P>mp;//记录区间[l,r]上的最大连续和的始末点pair(s,e) 
    13 
    14 int Maxsum(int l,int r)
    15 {
    16     if(l==r)//返回条件 
    17     {
    18         mp[P(l,r)]=P(1,r);
    19         return a[l];
    20     }    
    21     
    22     int m=(l+r)/2;
    23     int t=Maxsum(l,m),p=Maxsum(m+1,r);//左边和右边的最大连续和 
    24     int cmp,flag=0;
    25     if(t<p)
    26     {
    27         cmp=p;
    28         mp[P(l,r)]=mp[P(m+1,r)];
    29         flag=1;
    30     }
    31     else
    32     {
    33         cmp=t;
    34         mp[P(l,r)]=mp[P(l,m)];
    35     }
    36     //相连接的最大连续和 
    37     int L,R;
    38     for(int i=m,j=inf;i>=l;i--)    
    39         if(j>=a[i])    
    40             j=a[i],L=i;
    41     for(int i=m+1,j=-inf;i<=r;i++)
    42         if(j<a[i])    
    43             j=a[i],R=i;
    44     
    45     if(a[R]-a[L]>cmp||(a[R]-a[L]==cmp&&flag))//题中要求有多组相同结果时取最前面的结果,故而使用flag 
    46     {
    47         cmp=a[R]-a[L];
    48         mp[P(l,r)]=P(L+1,R);
    49     }
    50     
    51     return cmp; 
    52 }
    53 int main()
    54 {
    55     int test,kase=0;
    56     scanf("%d",&test);
    57     
    58     while(test--)
    59     {
    60         scanf("%d",&n);
    61         
    62         for(int i=1;i<=n;i++)    scanf("%d",&a[i]),a[i]+=a[i-1];
    63         
    64         printf("Case %d:
    %d",++kase,Maxsum(1,n));
    65         //注意Maxsum过程中求得的mp,所以不能把这两行放在一起输出 
    66         printf(" %d %d
    ",mp[P(1,n)].first,mp[P(1,n)].second);
    67         if(test)    printf("
    ");    
    68         
    69         mp.clear();
    70     }
    71     
    72     return 0;
    73 }

    4.O(n)算法

    还是以i为起点j为终点,则sum[ j ] - sum[ i-1 ]最大(参见第2种讨论)只要路过时顺便把在j之前最小的sum[ i-1 ]记录一下,就不需要遍历一遍了,直接减即可。代码中变量有点凌乱,见谅:

     1 //31ms
     2 #include<cstdio>
     3 
     4 int a[100005];
     5 int n,test,kase;
     6 
     7 int main()
     8 {
     9     scanf("%d",&test);
    10     while(test--)
    11     {
    12         printf("Case %d:
    ",++kase);
    13         
    14         scanf("%d",&n);
    15         for(int i=1;i<=n;i++)    scanf("%d",&a[i]),a[i]+=a[i-1];
    16         
    17         int s=0,e=0,minn=0,ans=-0x7fffffff;
    18         //s即start,e即end,代表始末下标;
    19         //minn是从j∈[1,i)中最小的a[j],ans为最大连续和初始值设为最小以更新 
    20         for(int i=1,j=0;i<=n;i++)//j作为临时记录用 
    21         {
    22             if(a[i]-minn>ans)    ans=a[i]-minn,s=j,e=i;//更新结果 
    23             if(minn>a[i])    minn=a[i],j=i;//更新minn 
    24         }
    25 
    26         printf("%d %d %d
    ",ans,++s,e);
    27         if(test)    printf("
    ");
    28     }
    29     return 0;
    30 }

    5.减少空间使用

    不需要开数组,只要贪心地每读入一个数,sum就加上这个数,若是比ans大,则ans更新为sum;若是sum<0了,则sum置0,因为前面一堆负数只会是后面的正数的累赘,不可能比后面的正数更优。至于后面的正数能不能把ans更新,就要看它能力了。

    这依旧是O(n)的算法,所以运行时间没变,但是减少了空间的使用!

    另,此代码中使用了读入挂来减少输入的所需时间,使得评测结果更优。虽然网上有很多快速读入的模板,不过AlphaWA感觉有点长有点乱,就东拼西凑瞎搞了一个。如果此种写法有bug,希望同学们指出!

     1 //31ms,scanf替换为普通getchar快速读为46ms,替换为fread快速读为0ms 
     2 #include<cstdio>
     3 
     4 //以下变量均为读入挂所需 
     5 const int maxl=1e2;
     6 //这里maxl是每次fread分块读入输入文件的长度,赋值为多少都可以 
     7 //由于有pos==len时pos归零的操作,可以使一个长文件分为若干个长度为maxl的文件读入 
     8 int pos,len;
     9 char buf[maxl];
    10 
    11 int xchar()
    12 {
    13     if(pos==len)    pos=0,len=fread(buf,1,maxl,stdin);
    14     return buf[pos++];
    15 }
    16 int read()
    17 {
    18     int x=0,s=1,c=xchar();
    19     while(c<=32)    c=xchar();
    20     if(c=='-')    s=-1,c=xchar();
    21     for(;c>='0'&&c<='9';c=xchar())    x=x*10+c-'0';
    22     return x*s;
    23 }
    24 int main()
    25 {
    26     int test,kase=0;
    27     test=read();
    28 
    29     while(test--)
    30     {
    31         int n,sum=0,ans=-1001,s,e,temp=1;
    32         n=read();
    33         
    34         for(int i=1;i<=n;i++)
    35         {
    36             int a;
    37             a=read();
    38             sum+=a;
    39             if(sum>ans)
    40             {
    41                 ans=sum;
    42                 s=temp;
    43                 e=i;
    44             }
    45             if(sum<0)
    46             {
    47                 sum=0;
    48                 temp=i+1;
    49             }
    50         }
    51         
    52         printf("Case %d:
    %d %d %d
    ",++kase,ans,s,e);
    53         if(test)    printf("
    ");
    54     }
    55     
    56     return 0;
    57 }

    END~

  • 相关阅读:
    打造vim IDE
    Shell常用快捷键
    centos修改SSH端口并禁用root远程登录
    Java集合图谱
    github密钥
    创建Node.js TypeScript后端项目
    Shell的类型
    Mac新系统常用设置
    mac svn无法保存密码,JetBrains IDE(WebStrom、IntelliJ IDEA) 反复提示输入密码
    vim快捷键
  • 原文地址:https://www.cnblogs.com/AlphaWA/p/9250010.html
Copyright © 2020-2023  润新知