1. 程式人生 > >HDU-1003:Max Sum(優化)

HDU-1003:Max Sum(優化)

with space bug 初始 problem -a 劉汝佳 但是 n)

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:\n最大連續和 連續和的起始下標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(n2)。然而面對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:\n%d",++kase,Maxsum(1,n));
65         //註意Maxsum過程中求得的mp,所以不能把這兩行放在一起輸出 
66         printf(" %d %d\n",mp[P(1,n)].first,mp[P(1,n)].second);
67         if(test)    printf("\n");    
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:\n",++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\n",ans,++s,e);
27         if(test)    printf("\n");
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:\n%d %d %d\n",++kase,ans,s,e);
53         if(test)    printf("\n");
54     }
55     
56     return 0;
57 }

END~

HDU-1003:Max Sum(優化)