题意翻译
题意
读入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;
}