HDOJ 1003:最大子序列|最大子串|最大连续和

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的算法。

 
  1. #include <iostream>

  2. using namespace std;

  3.  
  4. int main()

    扫描二维码关注公众号,回复: 2345825 查看本文章
  5. {

  6. int t;

  7. cin>>t;

  8. for(int i=0;i<t;i++)

  9. {

  10. int n;

  11. cin>>n;

  12. int sum = 0, max = -99999;

  13. int curhead=1, rear=1, head=1;

  14. for(int j=0;j<n;j++)

  15. {

  16. int temp;

  17. cin>>temp;

  18. if(sum<0)

  19. {

  20. curhead = j+1;

  21. sum = temp;

  22.  
  23. }else

  24. {

  25. sum += temp;

  26. }

  27. if(sum>max)

  28. {

  29. rear = j + 1;

  30. head = curhead;

  31. max = sum;

  32. }

  33. }

  34. cout<<"Case "<<i+1<<":"<<endl;

  35. cout<<max<<' '<<head<<' '<<rear<<endl;

  36. if(i!=t-1)

  37. cout<<endl;

  38. }

  39. }


 

思路:

1、brute force起码是n平方以上的复杂度。

2、可以用分治的方法去做,n*log(n)的复杂度。

将原串分为左右两个子串,最大子串有3种可能:

2.1:在左子串中

2.2:在右子串中

2.3:最大子串同时在左右子串中。这种情况需要一个有固定边界的求最大子串的函数

我没有实现这个,看着有点复杂

3、动态规划的方法:

我们考虑最后一个元素arr[n-1]与最大子数组的关系,有如下三种情况:

  1. arr[n-1]单独构成最大子数组
  2. 最大子数组以arr[n-1]结尾
  3. 最大子数组跟arr[n-1]没关系,最大子数组在arr[0-n-2]范围内,转为考虑元素arr[n-2]

从上面我们可以看出,问题分解成了三个子问题,最大子数组就是这三个子问题的最大值,现假设:

  1. 以arr[n-1]为结尾的最大子数组和为End[n-1]
  2. 在[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

All[i] = max{ arr[i],End[i-1]+arr[i],All[i-1] }

 
  1. /* DP base version*/

  2. #define max(a,b) ( a > b ? a : b)

  3.  
  4. int Maxsum_dp(int * arr, int size)

  5. {

  6. int End[30] = {-INF};

  7. int All[30] = {-INF};

  8. End[0] = All[0] = arr[0];

  9.  
  10. for(int i = 1; i < size; ++i)

  11. {

  12. End[i] = max(End[i-1]+arr[i],arr[i]);

  13. All[i] = max(End[i],All[i-1]);

  14. }

  15. return All[size-1];

  16. }

仔细看上面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了就自当前元素从新开始,否则就一直累加,取其中的最大值便求得解。

这个是理论基础,我们有更加直观的做法:

 
  1. /* 最大子数组 返回起始位置 */

  2. void Maxsum_location(int * arr, int size, int & start, int & end)

  3. {

  4. int maxSum = -INF;

  5. int sum = 0;

  6. int curstart = start = 0; /* curstart记录每次当前起始位置 */

  7. for(int i = 0; i < size; ++i)

  8. {

  9. if(sum < 0)

  10. {

  11. sum = arr[i];

  12. curstart = i; /* 记录当前的起始位置 */

  13. }else

  14. {

  15. sum += arr[i];

  16. }

  17. if(sum > maxSum)

  18. {

  19. maxSum = sum;

  20. start = curstart; /* 记录并更新最大子数组起始位置 */

  21. end = i;

  22. }

  23. }

  24. }


为什么可以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了

 
  1. #include <iostream>

  2. using namespace std;

  3.  
  4. int main()

  5. {

  6. int t;

  7. cin>>t;

  8. for(int i=0;i<t;i++)

  9. {

  10. int n;

  11. cin>>n;

  12. int sum = 0, max = -99999;

  13. int curhead=1, rear=1, head=1;

  14. for(int j=0;j<n;j++)

  15. {

  16. int temp;

  17. cin>>temp;

  18. if(sum<0)

  19. {

  20. curhead = j+1;

  21. sum = temp;

  22. }else

  23. {

  24. sum += temp;

  25. }

  26. if(sum>max)

  27. {

  28. rear = j + 1;

  29. head = curhead;

  30. max = sum;

  31. }

  32. }

  33. cout<<"Case "<<i+1<<":"<<endl;

  34. cout<<max<<' '<<head<<' '<<rear<<endl;

  35. if(i!=t-1)

  36. cout<<endl;

  37. }

  38. }

猜你喜欢

转载自blog.csdn.net/u013862444/article/details/81099197