子序列的和 | ||||||
|
||||||
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); } }