P1115 最大子段和
链接:https://www.luogu.com.cn/problem/P1115
方法一:0分代码
1 #include<bits/stdc++.h> 2 using namespace std; 3 int n, a[210000]; 4 int ans=-(1<<30); 5 int sum(int l, int r){ 6 int ret=0; 7 for(int i=l; i<=r; i++)ret+=a[i]; 8 return ret; 9 } 10 int main() 11 { 12 cin>>n; 13 for(int i=1; i<=n; i++)cin>>a[i]; 14 for(int i=1; i<=n; i++) 15 for(int j=i; j<=n; j++) 16 ans=max(ans,sum(i, j)); 17 cout<<ans; 18 return 0; 19 }
方法二:使用前缀和,但无优化(40分代码)
1 #include<bits/stdc++.h> 2 using namespace std; 3 int n, a[210000], presum[210000]; 4 int ans=-(1<<30);//答案求最大值,初始化为最小值 5 int main() 6 { 7 cin>>n; 8 for(int i=1; i<=n; i++)cin>>a[i]; 9 for(int i=1; i<=n; i++)presum[i]=presum[i-1]+a[i];//求前缀和 10 for(int i=1; i<=n; i++) 11 for(int j=i; j<=n; j++) 12 ans=max(ans,presum[j]-presum[i-1]); 13 cout<<ans; 14 return 0; 15 }
方法三:前缀和加优化(100分)
1 #include<bits/stdc++.h> 2 using namespace std; 3 int n, a[210000], psum[210000]; 4 int minl;//用于存放左端点最小的前缀和值 5 int ans=-(1<<30); 6 int main() 7 { 8 cin>>n; 9 for(int i=1; i<=n; i++){ 10 cin>>a[i]; 11 psum[i]=psum[i-1]+a[i];//输入同时求出前缀和 12 } 13 minl=0; 14 for(int i=1; i<=n; i++){ 15 ans=max(ans, psum[i]-minl);//求最大的区间和 16 minl=min(minl, psum[i]);//依次枚举最小左端点 17 } 18 cout<<ans; 19 return 0; 20 }
方法三:分治
你应该听说过分治法,正是:分而治之。我们有一个很复杂的大问题,很难直接解决它,但是我们发现可以把问题划分成子问题,如果子问题规模还是太大,并且它还可以继续划分,那就继续划分下去。直到这些子问题的规模已经很容易解决了,那么就把所有的子问题都解决,最后把所有的子问题合并,我们就得到复杂大问题的答案了。可能说起来简单,但是仍不知道怎么做,接下来分析这个问题:
首先,我们可以把整个序列平均分成左右两部分,答案则会在以下三种情况中:
1、所求序列完全包含在左半部分的序列中。
2、所求序列完全包含在右半部分的序列中。
3、所求序列刚好横跨分割点,即左右序列各占一部分。
前两种情况和大问题一样,只是规模小了些,如果三个子问题都能解决,那么答案就是三个结果的最大值。我们主要研究一下第三种情况如何解决:
我们只要计算出:以分割点为起点向左的最大连续序列和、以分割点为起点向右的最大连续序列和,这两个结果的和就是第三种情况的答案。因为已知起点,所以这两个结果都能在O(N)的时间复杂度能算出来。
递归不断减小问题的规模,直到序列长度为1的时候,那答案就是序列中那个数字。
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 //N是数组长度,num是待计算的数组,放在全局区是因为可以开很大的数组 5 int N, num[16777216]; 6 7 int solve(int left, int right) 8 { 9 //序列长度为1时 10 if(left == right) 11 return num[left]; 12 13 //划分为两个规模更小的问题 14 int mid = left + right >> 1; 15 int lans = solve(left, mid); 16 int rans = solve(mid + 1, right); 17 18 //横跨分割点的情况 19 int sum = 0, lmax = num[mid], rmax = num[mid + 1]; 20 for(int i = mid; i >= left; i--) { 21 sum += num[i]; 22 if(sum > lmax) lmax = sum; 23 } 24 sum = 0; 25 for(int i = mid + 1; i <= right; i++) { 26 sum += num[i]; 27 if(sum > rmax) rmax = sum; 28 } 29 30 //答案是三种情况的最大值 31 int ans = lmax + rmax; 32 if(lans > ans) ans = lans; 33 if(rans > ans) ans = rans; 34 35 return ans; 36 } 37 38 int main() 39 { 40 //输入数据 41 scanf("%d", &N); 42 for(int i = 1; i <= N; i++) 43 scanf("%d", &num[i]); 44 45 printf("%d\n", solve(1, N)); 46 47 return 0; 48 }
方法五:动态规划
1 #include<bits/stdc++.h> 2 using namespace std; 3 int k; 4 int a[200005]; 5 int dp[200005];//当前位置之前的最大字段和 6 int main() 7 { 8 scanf("%d",&k); 9 for(int i=0; i<k; i++) 10 scanf("%d",&a[i]); 11 dp[0]=a[0]; 12 int ans=dp[0]; 13 for(int i=1; i<k; i++) 14 { 15 dp[i]=max(a[i], dp[i-1]+a[i]);//dp[i]与a[i]有关 16 ans=max(ans, dp[i]); 17 } 18 //(int i=0; i<k; i++)printf("%d ",dp[i]); 19 //printf("\n"); 20 printf("%d",ans); 21 return 0; 22 }