HDOJ 1003 最大子序列和

这道关于DP的经典题终于花时间弄明白了。一定要记录一下啊。题目链接

思路

对这个题目,其实最重要的是思路。参考了此博客上面的思路,在这里对DP求解的思路做一些整理。


设数组 a r r 的长度为 n ,下标从 0 n 1 ,则最后一个元素表示为 a r r [ n 1 ] 。对连续和最大的子数组(后称”最大子数组“),最后一个元素 a r r [ n 1 ] 有如下三种情况:

{ a r r [ n 1 ] 单独构成最大子数组 最大子数组以 a r r [ n 1 ] 结尾 a r r [ n 1 ] a r r [ 0 n 2 ] a r r [ n 2 ]

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

a r r [ n 1 ] 为结尾的最大子数组和为 E n d [ n 1 ]
[ 0 , n 1 ] 范围内的最大子数组和为 A l l [ n 1 ]

如果最大子数组跟最后一个元素无关,即最大和为 A l l [ n 2 ] (存在范围为 [ 0 , n 2 ] ),则解 A l l [ n 1 ] 为三种情况的最大值,即

A l l [ n 1 ] = m a x { a r r [ n 1 ] E n d [ n 1 ] A l l [ n 2 ]

从后向前考虑,初始化的情况分别为 a r r [ 0 ] ,以 a r r [ 0 ] 结尾,即 E n d [ 0 ] = a r r [ 0 ] ,最大和范围在[0,0]之内,即 A l l [ 0 ] = a r r [ 0 ] 。根据上面分析,给出状态方程:

A l l [ i ] = m a x { a r r [ i ] E n d [ i 1 ] + a r r [ i ] A l l [ i 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]},如果 E n d [ i 1 ] < 0 ,那么 E n d [ i ] = a r r [ i ] ,什么意思?

E n d [ i ] 表示以i元素为结尾的子数组和,如果某一位置使得它小于0了,那么就自当前的 a r r [ i ] 从新开始,且 E n d [ i ] 最初是从 a r r [ 0 ] 开始累加的

所以这可以启示我们:我们只需从头遍历数组元素,并累加求和,如果和小于0了就自当前元素从新开始,否则就一直累加,取其中的最大值便求得解。

为什么当sum<0时可以舍弃序列并重新扫描呢?

证明如下:

i 表示子序列的起始下标, j 表示子序列的终止下标。

  • 当我们得到一个子序列,如果子序列的第一个数是非正数,那么可以舍去,即 i = i + 1

  • 当一个子序列的前 n 个元素和为非正数时,是否也可以舍去呢?答案是可以的。

k [ i , j ] s u m ( a , b ) = i = a b a r r [ i ] 。故得前提条件为: s u m ( i , j ) < 0
s u m ( i , k ) > 0 s u m ( i , k ) + s u m ( k , j ) = s u m ( i , j )
s u m ( k , j ) < s u m ( i , j ) < 0
所以如果把 k j 的序列附加到 j 之后的序列上,只会使序列越来越小。所以 i j 的序列都可以舍去。

代码

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();
            }
        }
    }
}

猜你喜欢

转载自blog.csdn.net/asahinokawa/article/details/80845980