单调队列,dp——POJ - 3017

题目含义

给出一堆数字,要求分成几个区间,并且每个区间的和不大于m,并求每个区间的最大值之和最小

题目分析

动态转移方程是dp[i]=dp[j]+max(a[j+1],a[j+2],...,a[i])

可以理解为,一个长的数列的值等于一个短的数列的值加上剩下的数的最大值

但是,sum[i]-sum[j]必须小于m,才能把max单独划出来

即便如此,我们也不知道哪一个j才是答案所划分的那个j

——————————————————————

首先确定的是,a[j+1]到a[i]的和必须要求小于m

那么假定a[k]是里面的最大值,如果把j+1,...,k-1分到左边去,那么右边的值不变,左边的值可能增加,不满足求最小值的要求

那么把j+1,...,k移到左边去后,就变成了dp[k]+max(a[k+1],..,a[i])右边是仅次于a[k]的最大值

一直分,最后就是dp[i-1]+a[i]

也就是说右边的取值遍历了从a[k]到a[i]的这样一个单调递减数列

所以我们可以用单调队列来求取

题目代码

#include<iostream>
#include<stdio.h>
#include<string.h>
using namespace std;
typedef long long LL;
const int maxn=1e5+7;
LL m,dp[maxn],que[maxn],a[maxn],sum[maxn];
int n;
int main(){
    scanf("%d%I64d",&n,&m);
    int flag=1;
    for(int i=1;i<=n;i++){
        scanf("%lld",&a[i]);
        sum[i]=sum[i-1]+a[i];
        if(a[i]>m)flag=0;
    }
    if(!flag){
        printf("-1\n");
        return 0;
    }
    int head=1,tail=1,t=0;
    que[head]=1;
    dp[1]=a[1];
    for(int i=2;i<=n;i++){
        while(t<i&&sum[i]-sum[t]>m)t++;
        while(head<=tail&&a[que[tail]]<=a[i])tail--;
        que[++tail]=i;
        while(head<=tail&&sum[i]-sum[que[head]-1]>m)head++;
        dp[i]=dp[t]+a[que[head]];
        for(int j=head;j<tail;j++)
            dp[i]=min(dp[i],dp[que[j]]+a[que[j+1]]);
    }
    printf("%lld\n",dp[n]);
}

 

posted @ 2019-07-26 19:57  helman78  阅读(98)  评论(0编辑  收藏  举报