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
照抄一个DP的算法。
-
#include <iostream>
-
using namespace std;
-
int main()
-
{
-
int t;
-
cin>>t;
-
for(int i=0;i<t;i++)
-
{
-
int n;
-
cin>>n;
-
int sum = 0, max = -99999;
-
int curhead=1, rear=1, head=1;
-
for(int j=0;j<n;j++)
-
{
-
int temp;
-
cin>>temp;
-
if(sum<0)
-
{
-
curhead = j+1;
-
sum = temp;
-
}else
-
{
-
sum += temp;
-
}
-
if(sum>max)
-
{
-
rear = j + 1;
-
head = curhead;
-
max = sum;
-
}
-
}
-
cout<<"Case "<<i+1<<":"<<endl;
-
cout<<max<<' '<<head<<' '<<rear<<endl;
-
if(i!=t-1)
-
cout<<endl;
-
}
-
}
思路:
1、brute force起码是n平方以上的复杂度。
2、可以用分治的方法去做,n*log(n)的复杂度。
将原串分为左右两个子串,最大子串有3种可能:
2.1:在左子串中
2.2:在右子串中
2.3:最大子串同时在左右子串中。这种情况需要一个有固定边界的求最大子串的函数
我没有实现这个,看着有点复杂
3、动态规划的方法:
我们考虑最后一个元素arr[n-1]与最大子数组的关系,有如下三种情况:
- arr[n-1]单独构成最大子数组
- 最大子数组以arr[n-1]结尾
- 最大子数组跟arr[n-1]没关系,最大子数组在arr[0-n-2]范围内,转为考虑元素arr[n-2]
从上面我们可以看出,问题分解成了三个子问题,最大子数组就是这三个子问题的最大值,现假设:
- 以arr[n-1]为结尾的最大子数组和为End[n-1]
- 在[0-n-1]范围内的最大子数组和为All[n-1]
如果最大子数组跟最后一个元素无关,即最大和为All[n-2](存在范围为[0-n-2]),则解All[n-1]为三种情况的最大值,即All[n-1] = max{ arr[n-1],End[n-1],All[n-2] }。从后向前考虑,初始化的情况分别为arr[0],以arr[0]结尾,即End[0] = arr[0],最大和范围在[0,0]之内,即All[0]=arr[0]。根据上面分析,给出状态方程:
1 |
|
-
/* DP base version*/
-
#define max(a,b) ( a > b ? a : b)
-
int Maxsum_dp(int * arr, int size)
-
{
-
int End[30] = {-INF};
-
int All[30] = {-INF};
-
End[0] = All[0] = arr[0];
-
for(int i = 1; i < size; ++i)
-
{
-
End[i] = max(End[i-1]+arr[i],arr[i]);
-
All[i] = max(End[i],All[i-1]);
-
}
-
return All[size-1];
-
}
仔细看上面DP方案的代码,End[i] = max{arr[i],End[i-1]+arr[i]},如果End[i-1]<0,那么End[i]=arr[i],什么意思?End[i]表示以i元素为结尾的子数组和,如果某一位置使得它小于0了,那么就自当前的arr[i]从新开始,且End[i]最初是从arr[0]开始累加的,所以这可以启示我们:我们只需从头遍历数组元素,并累加求和,如果和小于0了就自当前元素从新开始,否则就一直累加,取其中的最大值便求得解。
这个是理论基础,我们有更加直观的做法:
-
/* 最大子数组 返回起始位置 */
-
void Maxsum_location(int * arr, int size, int & start, int & end)
-
{
-
int maxSum = -INF;
-
int sum = 0;
-
int curstart = start = 0; /* curstart记录每次当前起始位置 */
-
for(int i = 0; i < size; ++i)
-
{
-
if(sum < 0)
-
{
-
sum = arr[i];
-
curstart = i; /* 记录当前的起始位置 */
-
}else
-
{
-
sum += arr[i];
-
}
-
if(sum > maxSum)
-
{
-
maxSum = sum;
-
start = curstart; /* 记录并更新最大子数组起始位置 */
-
end = i;
-
}
-
}
-
}
为什么可以sum<0,就舍弃,重新开始扫描呢?以下证明
我们用i表示子序列的起始下标,j 表示子序列的终止下标。
原理是,当我们得到一个子序列,如果子序列的第一个数是非正数,那么可以舍去,即i++
当一个子序列的前n个元素和为非正数时,是否也可以舍去呢?答案是可以的。
假设k 是i到j中任意一个下标。Sum( a, b ) 表示子序列第a个元素到第b个元素之和。由于加到第j个元素,子序列才开始为负数,所以Sum( i, k ) > 0,Sum( i, k ) + Sum( k, j ) = Sum( i, j ) ,所以Sum( k, j ) < Sum( i, j ) < 0
所以如果把 k到j的序列附加到j之后的序列上,只会使序列越来越小。所以i到j的序列都可以舍去。
参考了很多人的总结和代码,我自己写的基本都WA ,最后几乎照抄了人家的代码ac了
-
#include <iostream>
-
using namespace std;
-
int main()
-
{
-
int t;
-
cin>>t;
-
for(int i=0;i<t;i++)
-
{
-
int n;
-
cin>>n;
-
int sum = 0, max = -99999;
-
int curhead=1, rear=1, head=1;
-
for(int j=0;j<n;j++)
-
{
-
int temp;
-
cin>>temp;
-
if(sum<0)
-
{
-
curhead = j+1;
-
sum = temp;
-
}else
-
{
-
sum += temp;
-
}
-
if(sum>max)
-
{
-
rear = j + 1;
-
head = curhead;
-
max = sum;
-
}
-
}
-
cout<<"Case "<<i+1<<":"<<endl;
-
cout<<max<<' '<<head<<' '<<rear<<endl;
-
if(i!=t-1)
-
cout<<endl;
-
}
-
}