最大子序和--AT2412--由特殊到一般--Sabrina--Sabrinadol

题意翻译
题意
读入n个整数的数列a1,a2,…,an和正整数k(1<=k<=n),请输出连续排列的k个整数的和的最大值

输入
第一行是正整数n(1<=n<=100000)和正整数k(1<=k<=n) 第二行以后的第1+i(1<=i<=n)至最后一行为数列

输出
仅一行,仅包括最大值。

样例输入
5 3 2 5 -4 10 3

样例输出
11

由 @UMR 提供翻译

题目描述
输入输出格式
输入格式:
输出格式:
输入输出样例
暂无测试点
1.准确来说这是一道假装单调队列的水题,因为k是限定的我们直接模拟也是O(n)的复杂度,完全不虚

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#define maxn 300005
using namespace std;
int n,m;
long long int a[maxn];
long long int q[maxn];
int head=1;
int tail=1;
long long int lmaxn=-0xcf;
void begin(int x)//初始化单调队列
{
	for(int i=1;i<=x;i++)
	{
		q[tail]=a[i];
		tail++; 
	}
}
void solve(int x)
{
	long long int sum=0;
	long long 	int suml=-0xcf;
	for(int i=1;i<=x-1;i++)
	sum+=q[i];
	if(sum>suml) suml=sum;
	for(int i=x;i<=n;i++)
	{
		int kx=q[head];
		q[tail]=a[i];
		tail++;
		head++;
		if(a[i]>kx)
		{
			sum=0;
			for(int k=head;k<tail;k++)
			sum+=q[k];//如果发现有可能的最优解就马上计算比较
		}
		if(suml<sum) suml=sum;
	}
	if(suml>lmaxn) lmaxn=suml;
}
int main()
{
//	freopen("sum.in","r",stdin);
//	freopen("sum.out","w",stdout);
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	{
		scanf("%lld",&a[i]);
		if(lmaxn<a[i]) lmaxn=a[i];
	}	

	{
		memset(q,0,sizeof(q));
		head=1;
		tail=1;
		begin(m);
		solve(m+1);	
	}
	cout<<lmaxn<<endl;
	//fclose(stdin);
	//fclose(stdout);
	return 0;
}

做完这道题,大家可以想一想如果k变为小于等于k,怎么办呢,如下题

Description 输入一个长度为n的整数序列,从中找出一段不超过m的连续子序列,使得整个序列的和最大。 例如 1,-3,5,1,-2,3 当m=4时,S=5+1-2+3=7 当m=2或m=3时,S=5+1=6。
Input 第一行两个数n,m(n,m<=300000) 第二行有n个数,要求在n个数找到最大子序和。
Output 一个数,即最大子序和。
Sample Input 6 4 1 -3 5 1 -2 3
Sample Output 7
当然暴力一定是可以拿几十分的
只需要在原来的代码中加一重循环就好了

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#define maxn 300005
using namespace std;
int n,m;
long long int a[maxn];
long long int q[maxn];
int head=1;
int tail=1;
long long int lmaxn=-0xcf;
void begin(int x)
{
	for(int i=1;i<=x;i++)
	{
		q[tail]=a[i];
		tail++; 
	}
}
void solve(int x)
{
	long long int sum=0;
	long long 	int suml=-0xcf;
	for(int i=1;i<=x-1;i++)
	sum+=q[i];
	if(sum>suml) suml=sum;
	for(int i=x;i<=n;i++)
	{
		int kx=q[head];
		q[tail]=a[i];
		tail++;
		head++;
		if(a[i]>kx)
		{
			sum=0;
			for(int k=head;k<tail;k++)
			sum+=q[k];
		}
		if(suml<sum) suml=sum;
	}
	if(suml>lmaxn) lmaxn=suml;
}
int main()
{
//	freopen("sum.in","r",stdin);
//	freopen("sum.out","w",stdout);
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	{
		scanf("%lld",&a[i]);
		if(lmaxn<a[i]) lmaxn=a[i];
	}	
	for(int i=2;i<=m;i++)
	{
		memset(q,0,sizeof(q));
		head=1;
		tail=1;
		begin(i);
		solve(i+1);	
	}
	cout<<lmaxn<<endl;
//	fclose(stdin);
//	fclose(stdout);
	return 0;
}

不过要想得到AC,那就必须要优化,加入一点动归的思想
我们可以分析一下
1.要求一个区间和,可不可以看成求这个区间两端的前缀和之差
求前缀和直接预处理就行了

	for(int i=1;i<=n;i++)
	{
		scanf("%lld",&a[i]);
		a[i]+=a[i-1];
	}

2.其次,既然是小于等于m,那么我们是不是可以找
i-m到i-1这个区间内的最小前缀和,然后在做差,是不是就能够得到最优解了
3.当然,寻找最小前缀和是技巧的关键所在,这里我们可以用单调队列来实现,通过维护一个在合法范围内(>=i-m)的单增序列,在每次使用时,直接减去序列头所代表的前缀和就行了

	while(head<=tail&&q[head]<i-m) head++;
	//q[head]<i-m就不能到i点 
		ans=max(ans,a[i]-a[q[head]]);//求差即为区间和 
		while(head<=tail&&a[q[tail]]>a[i]) tail--;
		//保持一个单调的单增数列 

附上AC代码

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#define maxn 300005
using namespace std;
int n,m;
long long int a[maxn];
long long int q[maxn];
int head=1;
int tail=1;
long long int ans=-0xcf;
int main()
{

	cin>>n>>m;
	for(int i=1;i<=n;i++)
	{
		scanf("%lld",&a[i]);
		a[i]+=a[i-1];
	}
	q[0]=0;
	for(int i=1;i<=n;i++)
	{
		while(head<=tail&&q[head]<i-m) head++;//q[head]<i-m就不能到i点 
		ans=max(ans,a[i]-a[q[head]]);//求差即为区间和 
		while(head<=tail&&a[q[tail]]>a[i]) tail--;//保持一个单调的单增数列 
		tail++;
		q[tail]=i;
	} 
	cout<<ans;
	return 0;
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值