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
Input
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
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(優化)