哈理工OJ 1522 子序列的和(单调队列)(dp)


子序列的和
Time Limit: 1000 MS Memory Limit: 32768 K
Total Submit: 289(61 users) Total Accepted: 71(43 users) Rating:  Special Judge: No
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);
    }
}







你可能感兴趣的:(单调队列,哈理工OJ1522,1522,1522,哈理工,1522,哈理工ACM)