poj3017 dp+单调队列

本文介绍了解决POJ3017问题的一种高效算法,通过DP结合单调队列优化实现。该问题要求将一串整数序列切割成若干连续子序列,确保各子序列和不超过给定值M,并找到一种切割方式使得各子序列最大值之和最小。

http://poj.org/problem?id=3017

Description

Given an integer sequence { an } of length N, you are to cut the sequence into several parts every one of which is a consecutive subsequence of the original sequence. Every part must satisfy that the sum of the integers in the part is not greater than a given integer M. You are to find a cutting that minimizes the sum of the maximum integer of each part.

Input

The first line of input contains two integer N (0 < N ≤ 100 000), M. The following line contains N integers describes the integer sequence. Every integer in the sequence is between 0 and 1 000 000 inclusively.

Output

Output one integer which is the minimum sum of the maximum integer of each part. If no such cuttings exist, output −1.

Sample Input

8 17
2 2 2 8 1 8 2 1

Sample Output

12
/**
poj3017  dp+单调队列优化
题目大意:给定一个数列,把这个数列进行分割,每个小段的和不能超过m,求如何分配能使得每段的最大值的总和最小
解题思路:这道题猛地一看好像一个最大值最小化,其实并不是,这道题是求所有段最大值的和最小。我们用dp来写
          dp[i]=dp[j]+max(x[j+1],x[j+2],x[j+3]...x[i]).其中(x[j+1],x[j+2],x[j+3]...x[i])<=m.复杂度是O(n^2)
          我们采取单调队列的思想优化一下,O(n)的复杂度
*/
#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <iostream>
using namespace std;
typedef long long LL;
const int maxn=100005;
LL a[maxn],q[maxn],m,dp[maxn];
int n;
int main()
{
    while(~scanf("%d%lld",&n,&m))
    {
        LL pos=0,last=0,cnt=0;
        int front=1,rear=0,flag=0;
        dp[0]=0;
        for(int i=1;i<=n;i++)
        {
            scanf("%lld",&a[i]);
            if(a[i]>m)flag=1;
            cnt+=a[i];
            while(cnt>m)cnt-=a[++pos];
            while(front<=rear&&a[q[rear]]<=a[i])
                rear--;
            q[++rear]=i;
            while(front<=rear&&q[front]<=pos)
                front++;
            last=pos;
           /// printf("(%d ",last+1);
            dp[i]=0x3f3f3f3f3f3f3f3f;
            for(int j=front;j<=rear;j++)
            {
                dp[i]=min(dp[i],dp[last]+a[q[j]]);
                last=q[j];
            }
            /// printf("%d %d)\n",i,dp[i]);
        }
        if(flag)
            printf("-1\n");
        else
            printf("%lld\n",dp[n]);
    }
    return 0;
}


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值