1049 最大子段和
基准时间限制:1 秒 空间限制:131072 KB 分值: 0 难度:基础题 收藏 关注
N个整数组成的序列a[1],a[2],a[3],…,a[n],求该序列如a[i]+a[i+1]+…+a[j]的连续子段和的最大值。当所给的整数均为负数时和为0。
例如:-2,11,-4,13,-5,-2,和最大的子段为:11,-4,13。和为20。
Input
第1行:整数序列的长度N(2 <= N <= 50000)
第2 - N + 1行:N个整数(-10^9 <= A[i] <= 10^9)
Output
输出最大子段和。
Input示例
6
-2
11
-4
13
-5
-2
Output示例
20
最基本的问题
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <string>
using namespace std;
int main(){
long long n,a,ans = -1e9,temp_sum = 0;
cin>>n;
for(int i = 0; i < n; i++){
cin>>a;
if(temp_sum < 0) temp_sum = a;
else temp_sum += a;
ans = max(ans,temp_sum);
}
cout<<ans<<endl;
return 0;
}
1050 循环数组最大子段和
基准时间限制:1 秒 空间限制:131072 KB 分值: 10 难度:2级算法题 收藏 关注
N个整数组成的循环序列a[1],a[2],a[3],…,a[n],求该序列如a[i]+a[i+1]+…+a[j]的连续的子段和的最大值(循环序列是指n个数围成一个圈,因此需要考虑a[n-1],a[n],a[1],a[2]这样的序列)。当所给的整数均为负数时和为0。
例如:-2,11,-4,13,-5,-2,和最大的子段为:11,-4,13。和为20。
Input
第1行:整数序列的长度N(2 <= N <= 50000)
第2 - N+1行:N个整数 (-10^9 <= S[i] <= 10^9)
Output
输出循环数组的最大子段和。
Input示例
6
-2
11
-4
13
-5
-2
Output示例
20
这个是上面的变形,这个我们还要求出最小子段和,然后用总和减去最小子段和比较最大子段和,看谁大就行
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <string>
using namespace std;
int main(){
long long n,a,ans1 = 0,ans2 = 0,maxsum =0,minsum = 0, sum = 0;
cin>>n;
for(int i = 0; i < n; i++){
cin>>a;
sum += a;
if(maxsum < 0) maxsum = a;
else maxsum += a;
if(minsum > 0) minsum = a;
else minsum += a;
ans1 = max(maxsum,ans1);
ans2 = min(minsum,ans2);
}
ans1 = ans1 > sum - ans2 ? ans1 : sum - ans2;
cout<<ans1<<endl;
return 0;
}
1065 最小正子段和
基准时间限制:1 秒 空间限制:131072 KB 分值: 20 难度:3级算法题 收藏 关注
N个整数组成的序列a[1],a[2],a[3],…,a[n],从中选出一个子序列(a[i],a[i+1],…a[j]),使这个子序列的和>0,并且这个和是所有和>0的子序列中最小的。
例如:4,-1,5,-2,-1,2,6,-2。-1,5,-2,-1,序列和为1,是最小的。
Input
第1行:整数序列的长度N(2 <= N <= 50000)
第2 - N+1行:N个整数
Output
输出最小正子段和。
Input示例
8
4
-1
5
-2
-1
2
6
-2
Output示例
1
这题没想像中那么简单,开始以为和求最大的差不多,后来随手捏了几个数据都不行,之前是方法是累加,现在要区间求
先求一下从第一位开始的到第i位的累加,
4,-1,5,-2,-1,2,6,-2 => 4 3 8 6 5 7 13 11
对这个累加的数列排个序,然后只要判断邻近的两个数是否可以组成序列,比如4和3就不可以,因为4 > 3而4对应下标为0,3对应为1。4和5就可以
解释一下为什么只需检查相邻2个数就可以,设ABC是排序后的结果,如果A同B不能组成序列,而A同C可以组成序列,那么B同C也可以组成序列,并且BC会是一个更优的解。
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <string>
#include <vector>
using namespace std;
vector<pair<long long,int> > vec;
int main(){
int n,a,ans = 1e9;
long long sum = 0;
cin>>n;
vec.push_back(make_pair(0,0));
for(int i = 1; i <= n; i++){
cin>>a;
sum += a;
vec.push_back(make_pair(sum,i));
}
sort(vec.begin(), vec.end());
for(int i = 1; i <= n; i++){
if(vec[i].second > vec[i-1].second){
int subsum = vec[i].first - vec[i-1].first;
if(subsum > 0){
ans = min(ans,subsum);
}
}
}
cout<<ans<<endl;
return 0;
}
1052 最大M子段和
基准时间限制:2 秒 空间限制:131072 KB 分值: 80 难度:5级算法题 收藏 关注
N个整数组成的序列a[1],a[2],a[3],…,a[n],将这N个数划分为互不相交的M个子段,并且这M个子段的和是最大的。如果M >= N个数中正数的个数,那么输出所有正数的和。
例如:-2 11 -4 13 -5 6 -2,分为2段,11 -4 13一段,6一段,和为26。
Input
第1行:2个数N和M,中间用空格分隔。N为整数的个数,M为划分为多少段。(2 <= N , M <= 5000)
第2 - N+1行:N个整数 (-10^9 <= a[i] <= 10^9)
Output
输出这个最大和
Input示例
7 2
-2
11
-4
13
-5
6
-2
Output示例
26
dp[i][j] 表示在前 i 个数中选取 j 组 且第 i 个数在最后一组中的 Max Sum Plus Plus
那么现在对于第i个数有两种决策
1, 第 i 个数和第 i-1 个数连接成一段 dp[i][j] = dp[i-1][j] + a[i]
2, 第 i 个数自己单独做一段 那么前面就需要有 j-1 段 dp[i][j] = max{dp[k][j-1] | j - 1<= k < i} + a[i]
那么也就有了状态转移方程 dp[i][j] = max(dp[i-1][j], max{dp[k][j-1] | j - 1<= k < i}) + a[i];
但是这样空间复杂度超了,可以用滚动数组优化
发现更新 dp[i][j] 的时候只用到了 dp[.][j] 和 dp[.][j-1] 里面的值 也就是 dp[.][0~j-2] 都已经没用了 那也就不用存这些没用的了 也就是j只用开两维就够了 也就是所谓的滚动数组了 用 dp[.][1] 和 dp[.][0] 来轮换表示 dp[.][j] 和 dp[.][j-1] 这样空间复杂度就降到了O(n) 这是可以接受的
那么我现在用两个数组来优化
用两个数组,pre[MAXN]和dp[MAXN]。
首先m次循环,第x次循环代表的是把整个序列分成x个子段所能得到的最大x子段和。
pre[i]数组记录的是,从第1个数到第i个数被分成x个子段所能得到的最大子段和。
假设当前已分成了x个子段(即最外层循环执行了x次),然后需要执行第x+1次时:
则,dp[i]的转移是从下列两种情况取max值。
1.前i-1个数分为x个子段得到的最大和pre[i-1]加上单独把input[i]作为第x+1个子段的开头;
2.从前i-1个数中已经分出了x+1个子段,且第x+1个子段的尾部需要加上input[i],即dp[i-1]+input[i]
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <limits.h>
using namespace std;
typedef long long LL;
#define MAXN 5010
const LL MIN_INF = -(1 << 30);
LL dp[MAXN], input[MAXN], pre[MAXN];
int n, m;
int main() {
scanf("%d%d", &n, &m);
dp[0] = MIN_INF;
pre[0] = 0;
for(int i = 1; i <= n; i++) {
scanf("%lld", &input[i]);
pre[i] = 0;
dp[i] = MIN_INF;
}
LL ans = MIN_INF;
while(m--) {
ans = MIN_INF;
for(int i = 1; i <= n; i++) {
if(i == 1) dp[i] = pre[i - 1] + input[i];
else {
if(dp[i - 1] > pre[i - 1]) {
dp[i] = dp[i - 1] + input[i];//第i个数与它的上一个数在同一子段内
} else {
dp[i] = pre[i - 1] + input[i];//从第i个分出一个新的子段
}
}
//dp[i] = max(dp[i - 1], pre[i - 1]) + input[i]; 或者直接这么取值也可
pre[i - 1] = ans;
ans = max(ans, dp[i]);
}
pre[n] = ans;
}
printf("%lld\n", ans);
return 0;
}
然后这题还要听说O(n)的算法,然而并不会….