子序列的和 | ||||||
| ||||||
Description | ||||||
输入一个长度为n的整数序列(A1,A2,……,An),从中找出一段连续的长度不超过m的子序列,使得这个子序列的和最大。 | ||||||
Input | ||||||
有多组测试数据,不超过20组测试数据
。
对于每组测试的第一行,包含两个整数n和m(n,m<=10^5),表示有n个数,子序列长度限制为m,表示这个序列的长度,第二行为n个数,每个数的范围为[-1000, 1000]。
| ||||||
Output | ||||||
对于每组测试数据,输出最大的子序列和,并换行。 | ||||||
Sample Input | ||||||
3 1
1 2 3
3 2
-1000 1000 1
|
这道题不同于普通的子序列和最大的题目,这里限制了子序列的长度,这里应用一个单调队列:顾名思义 单调队列,即单调的队列 。这里求最大值,我们就需要一个单调递增的队列。而且要限制里边的元素的个数不能超过限制个数。
这里我们分步详解:这里配合实例说:
3 2
3 -2 1
第一步:我们这里求出序列区间和:31 2;sum数组表示
第二步:向单调队列中插入元素0;
第三步:向队列中按序插入元素(当然是区间和的元素)(sum数组内的元素)如果队列中元素的个数超过了限制的个数,pop队头。至于插入的位子,不用多想也知道,既然是单调递增函数,我们这里可以从队尾向前扫,如果找到了大于这个元素的地方,插入进去。这个时候注意,队尾变成了这个数的位子。而且这里为什么要从队列后边pop元素呢,因为这些个元素用不上了。比如样例中的数据,先进队列的元素是3,同时当前子序列和最大是3.之后进来的元素是1.这里我们sum数组求的是区间子序列的和,也可以理解为除当前元素的之前所有元素和加上当前元素的和。如果这个和小于之前的和即sum【i】<sum【i-1】的时候说明了当前元素是个负数,不应该和前边的区间和放在一个子序列当中,他应该单独成一个序列(这里应用到了普通的dp求最大子序列和的思想在里边。)所以队列之前的元素,是直接抛弃就行了的,这也是为什么我们没有用C++的头文件调用queue队列的原因。
第四步:进行元素的值比较。
说的可能不是很清楚,大家可以对应代码细细琢磨,然后对应理解:
#include<stdio.h>
#include<string.h>
using namespace std;
struct node
{
int val,weizi;
}queue[100010],tmp;
int sum[100010];
int main()
{
int n,m;
while(~scanf("%d%d",&n,&m))
{
sum[0]=0;
for(int i=1;i<=n;i++)
{
int k;
scanf("%d",&k);
sum[i]=sum[i-1]+k;//求区间子序列和
}
int s,e;//队头队尾。
s=0;
e=-1;//初始化为-1;因为i=0的时候要0入队
int output=-0x1f1f1f1f;
for(int i=1;i<=n;i++)
{
while(s<=e&&queue[s].weizi<i-m)//这是pop队头元素,对队列元素的个数进行限制,并且达到了去旧的作用(旧了的元素没用了,因为序列长度有限制,不可能让序列和一直就这么加下去。)
{
s++;
}
while(s<=e&&sum[i-1]<queue[e].val)//这是在pop队尾元素,并且给当前要入队的元素找到自己该呆的位子。
{
e--;
}
tmp.weizi=i-1;
tmp.val=sum[i-1];
queue[++e]=tmp;//入队元素
//printf("%d\n",sum[i]-queue[s].val);
if(sum[i]-queue[s].val>output)//这是在计算限制长度的子序列区间和,可测试结果对应理解。
output=sum[i]-queue[s].val;
}
printf("%d\n",output);
}
}