最大子段和(洛谷--P1115)

(1)题目

来源

最大子段和 - 洛谷

题目描述

给出一个长度为 n 的序列 a,选出其中连续且非空的一段使得这段和最大。

输入格式

第一行是一个整数,表示序列的长度 n。

第二行有 n 个整数,第 i 个整数表示序列的第 i 个数字 ai​。

输出格式

输出一行一个整数表示答案。

输入样例

7

2 -4 3 -1 2 -4 3

输出样例

4

说明/提示

样例 1 解释

选取 [3,5]子段 {3,−1,2},其和为 4。

数据规模与约定

对于 40% 的数据,保证 n≤2×10^3。

对于 100% 的数据,保证 1≤n≤2×10^5,−10^4≤ai≤10^4。

(2)思路

1. 分析问题:分析已知和未知

首先,说明一下,我们讨论这个问题要经历以下步骤:

a. 枚举子段(头-尾)→b. 枚举子段(头-长度)→c. 前缀和(尾和-前头和)→d. 动态规划→e. 简化(去数组)

a. b. c. 步骤只能得40分,会有3个测试点不过(因为超时了),注意一下,开数组时开大一点,1≤n≤2×10^5

a. 枚举子段(头-尾)=>40分

枚举所有子段的头和尾,求出每个子段和,与maxi比较

注:max函数使用方法=> a=max(a,b) =>a等于a(原来)与b中最大的数

sum用于计算子段和

#include<iostream>
using namespace std;
int main()
{
	int n,a[200005],maxi=-100000005,sum=0;
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i];
	for(int i=1;i<=n;i++)//头 
	{
		for(int j=i;j<=n;j++)//尾 
		{
			sum=0;
			for(int k=i;k<=j;k++) sum+=a[k];//子段和 
			maxi=max(maxi,sum);
		}
	}
	cout<<maxi;
	return 0;
} 
b. 枚举子段(头-长度)=>40分

这里有2点要注意一下:

枚举长度时是从1到(n+1-j),长度会随着头的变化而变化;

求子段和时,应是从i(头)到(i+j-1),因为长度中包括子段的尾。

#include<iostream>
using namespace std;
int main()
{
	int n,a[200005],maxi=-100000005,sum=0;
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i];
	for(int i=1;i<=n;i++)//头 
	{
		for(int j=1;j<=n+1-i;j++)//长度 
		{
			sum=0;
			for(int k=i;k<=i+j-1;k++) sum+=a[k];//子段和 
			maxi=max(maxi,sum);
		}
	}
	cout<<maxi;
	return 0;
}
c. 前缀和(尾和-前头和)=>40分

前缀和通俗点来讲就是数组或数列中,从头的前一个数到这个数所有数字之和(包括头和这个数字)

通过前缀和,也可以找出每个子段,并求和即比较

注:从头的前一个数到这个数,而不是从头开始,是因为头的前缀和中包含头的数字,从头开始则把头的数字减掉了

#include<iostream>
using namespace std;
int a[200005],f[200005];
int main()
{
	int n,maxi=-100000005,sum=0;
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i];
	for(int i=1;i<=n;i++)//前缀和 
	{
		f[i]=f[i-1]+a[i];
	}
	for(int i=1;i<=n;i++)//头 
	{
		sum=0;
		for(int j=i;j<=n;j++)//尾 
		{
			sum=f[j]-f[i-1];//尾和-头和 
			maxi=max(maxi,sum);
		}
	}
	cout<<maxi;
	return 0;
}
d. 动态规划=>100分

 f[i]:从第1位到第 i 位中最大子段和

求出每个f[i],但f[1]=a[1];

再比较产生的新的子段和与前一个最大子段和,但通过下图可得,若f[i]+a[i]>f[i],f[i]=f[i]=a[i],若f[i]+a[i]<f[i],f[i]不变,这样可以减少时间复杂度;

最后比较maxi与f[i];

画图如下(数据为样例):

#include<iostream>
using namespace std;
int a[200005],f[200005];
int main()
{
	int n,maxi=-100000000;
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i]; 
	f[1]=a[1];
	for(int i=2;i<=n;i++)
	{
		f[i]=max(f[i-1]+a[i],a[i]);
		maxi=max(maxi,f[i]);
	}
	cout<<maxi;
	return 0;
}
e. 简化(去数组)=>100分

我们可以把动态规划的内容转换成不带数组的形式,如下

f只要这一位和前一位,根据算法,前一位的f可保留

a只用这一位就够了

所以f和a可以去数组

#include<iostream>
using namespace std;
int main()
{
	int n,a,f=0,maxi=-1000000000;
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		cin>>a;
		if(f+a>a) f=f+a;
		else f=a;
		maxi=max(maxi,f);
	}
	cout<<maxi;
	return 0;
}

2. 数据定义:已知和未知的取名和类型

n:序列长度

a:输入的数

f:从第1位到第 i 位中最大子段和

maxi=-1000000000:−10^4≤ai≤10^4,maxi范围较大,从-10^9开始

3. 数据输入:输入已知 

cin>>n;

for(int i=1;i<=n;i++)

{
        cin>>a;

}

4. 数据计算:数字建模+设计算法

代码较短,前面也已经分析的差不多了,就不写了

(3)完整AC代码

#include<iostream>
using namespace std;
int main()
{
	int n,a,f=0,maxi=-1000000000;
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		cin>>a;
		if(f+a>a) f=f+a;
		else f=a;
		maxi=max(maxi,f);
	}
	cout<<maxi;
	return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值