这道关于DP的经典题终于花时间弄明白了。一定要记录一下啊。题目链接。
思路
对这个题目,其实最重要的是思路。参考了此博客上面的思路,在这里对DP求解的思路做一些整理。
设数组 的长度为 ,下标从 到 ,则最后一个元素表示为 。对连续和最大的子数组(后称”最大子数组“),最后一个元素 有如下三种情况:
从上面我们可以看出,问题分解成了三个子问题,最大子数组就是这三个子问题的最大值,现假设:
以
为结尾的最大子数组和为
在
范围内的最大子数组和为
如果最大子数组跟最后一个元素无关,即最大和为 (存在范围为 ),则解 为三种情况的最大值,即
从后向前考虑,初始化的情况分别为
,以
结尾,即
,最大和范围在[0,0]之内,即
。根据上面分析,给出状态方程:
伪代码表示为:
/* 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]}
,如果
,那么
,什么意思?
表示以i元素为结尾的子数组和,如果某一位置使得它小于0了,那么就自当前的 从新开始,且 最初是从 开始累加的。
所以这可以启示我们:我们只需从头遍历数组元素,并累加求和,如果和小于0了就自当前元素从新开始,否则就一直累加,取其中的最大值便求得解。
为什么当sum<0时可以舍弃序列并重新扫描呢?
证明如下:
设 表示子序列的起始下标, 表示子序列的终止下标。
当我们得到一个子序列,如果子序列的第一个数是非正数,那么可以舍去,即
当一个子序列的前 个元素和为非正数时,是否也可以舍去呢?答案是可以的。
设
,
。故得前提条件为:
。
且
所以如果把
到
的序列附加到
之后的序列上,只会使序列越来越小。所以
到
的序列都可以舍去。
代码
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.util.Scanner;
public class Main {
public static void main(String[] args) throws FileNotFoundException {
Scanner sc = new Scanner(System.in);
//sc = new Scanner(new FileInputStream("src\\oj\\hdoj\\Q1003.txt"));
int N = sc.nextInt();
for (int l = 0; l < N; l++) {
int n = sc.nextInt();
int max = -999999, sum = 0, ci = 0, i = 0, j = 0;
for (int k = 0; k < n; k++) {
int tmp = sc.nextInt();
if (sum < 0){
sum = tmp;
ci = k;
} else {
sum += tmp;
}
if (sum > max){
j = k;
i = ci;
max = sum;
}
}
System.out.println("Case " + (l+1) + ":" );
System.out.println(max + " " + (i + 1) + " " + (j + 1));
if (l < N - 1) {
System.out.println();
}
}
}
}