PAT甲级——1007 Maximum Subsequence Sum

Given a sequence of K integers { N​1​​, N​2​​, ..., N​K​​ }. A continuous subsequence is defined to be { N​i​​, N​i+1​​, ..., N​j​​ } where 1≤i≤j≤K. The Maximum Subsequence is the continuous subsequence which has the largest sum of its elements. For example, given sequence { -2, 11, -4, 13, -5, -2 }, its maximum subsequence is { 11, -4, 13 } with the largest sum being 20.

Now you are supposed to find the largest sum, together with the first and the last numbers of the maximum subsequence.

Input Specification:

Each input file contains one test case. Each case occupies two lines. The first line contains a positive integer K (≤10000). The second line contains K numbers, separated by a space.

Output Specification:

For each test case, output in one line the largest sum, together with the first and the last numbers of the maximum subsequence. The numbers must be separated by one space, but there must be no extra space at the end of a line. In case that the maximum subsequence is not unique, output the one with the smallest indices i and j (as shown by the sample case). If all the K numbers are negative, then its maximum sum is defined to be 0, and you are supposed to output the first and the last numbers of the whole sequence.

Sample Input:

10
-10 1 2 3 4 -5 -23 3 7 -21

Sample Output:

10 1 4


思路:

         这道题求的是最大连续子序列的和,输出的时候还要输出连续子序列的首尾元素。那么需要分解为两个问题:第一个问题是求最大连续子序列的和,第二个问题是求子序列的首尾元素,这个问题相对简单。

        怎么求最大连续子序列的和呢?如果用枚举的方法,复杂度过高,是不能接受的。这里可以设置一个数组d[n],d[i]表示以下标为i的元素结尾的最大子序列的和,这样求出数组d[n]之后,再遍历一遍数组找出最大的元素就是最大连续子序列的和。求d[i]的时候用到了动态规划中的状态转移方程(动态规划用来解决有重叠子问题的问题,可以由递归或者递推实现,递推的时候就需要建立状态转移方程),设a[n]存放每个元素的值,当d[i-1]+a[i]<=a[i]的时候,我们取d[i]=a[i],否则就取d[i]=d[i-1]。

举个例子:

序列为  -10 1 2 3 4 -5 -23 3 7 -21  

初始条件:d[0]=a[0]=-10

d[0]+a[1]=-10+1=-9<a[1]=1  那么d[1]=1;

d[1]+a[2]=1+2=3>a[2]=2 那么d[2]=3;

d[2]+a[3]=3+3=6>a[3]=3 那么d[3]=6;

d[3]+a[4]=6+4=10>a[4]=4 那么d[4]=10;

d[4]+a[5]=10-5=5>a[5]=-5 那么d[5]=5;

d[5]+a[6]=5-23=-18>a[6]=-23 那么d[6]=-18;

d[6]+a[7]=-18+3=-15<a[7]=3 那么d[7]=3;

d[7]+a[8]=3+7=10>a[8]=7 那么d[8]=10;

d[8]+a[9]=10-21=-11>a[9] 那么d[9]=-11;

有相同的结果时需要输出序列索引最小的那一组,所以结果为10 1 4;

       怎么求序列的首尾元素呢,末尾元素就是a[i],那就求首元素就行了。这里开一个数组s[n],s[i]表示以i结尾的子序列的首元素在数组a中的下标。如果d[i-1]+a[i]>a[i] 那么s[i]=s[i-1],否则的话s[i]=i。这样就可以求出首元素在a[n]中的下标,输出a[s[i]]就可以了。

代码:

#include <iostream>
const int maxn = 10010;
int a[maxn], dp[maxn];//a[i]存放序列,dp[i]存放以a[i]结尾的连续序列的最大和
int s[maxn] = { 0 };//表示产生dp[i]的连续序列从a的哪一个元素开始

int main()
{
	int n;
	scanf("%d", &n);
	bool flag = false;//flag表示数组中的元素是否全部小于0
	for (int i = 0; i < n; i++)//读入序列
	{
		scanf("%d", &a[i]);
		if (a[i] >= 0) flag = true;
	}
	if (flag == false) {
		printf("0 %d %d\n", a[0], a[n - 1]);
		return 0;
	}

	//边界
	dp[0] = a[0];
	for (int i = 1; i < n; i++)
	{
		if (dp[i - 1] + a[i] > a[i])
		{
			dp[i] = dp[i - 1] + a[i];
			s[i] = s[i - 1];
		}
		else
		{
			dp[i] = a[i];
			s[i] = i;
		}

	}
	//因为dp[i]存放的是以a[i]结尾的连续序列的最大和
	//因此需要遍历i得到最大的才是结果
	int k = 0;
	for (int i = 1; i < n; i++)
	{
		if (dp[i] > dp[k]) {
			k = i;
		}
	}
	printf("%d %d %d\n", dp[k], a[s[k]], a[k]);
	return 0;

}

 

复习:

        其实最重要的是理解求最大连续子序列的思想,这里需要额外建立一个dp[]数组来记录以元素i结尾的最大子序列的和,而不能直接去在数组a[]上面去改变原来的数组,因为最后需要输出的是子序列首尾元素,所以要保留原来的数组a。看清题目要求啊,到底是输出数组元素还是数组元素的下标。

二刷代码:

#include <iostream>
using namespace std;
const int maxn = 10010;
int a[maxn],start[maxn],index=0,dp[maxn];

int main()
{
	int n;
	bool flag = false;
	scanf("%d", &n);
	for (int i = 0; i < n; i++)
	{
		scanf("%d", &a[i]);
		if (a[i] >= 0)
			flag = true;
		dp[i] = a[i];
		start[i] = i;
	}
	if (flag == false)
	{
		printf("%d %d %d", 0, a[0],a[n - 1]);
		return 0;
	}
	for (int i = 1; i < n; i++)
	{
		if (dp[i - 1] + dp[i] > dp[i])
		{
			dp[i] = dp[i - 1] + dp[i];
			start[i] = start[i - 1];
		}
			

		
	}
	int Max = dp[0];
	for (int i = 0; i < n; i++)
	{
		if (dp[i] > Max)
		{
			Max = dp[i];
			index = i;
		}
	}
	printf("%d %d %d", dp[index], a[start[index]], a[index]);
	return 0;
}

 

 

 

 

    

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值