poj 3017 Cut the Sequence 不用什么堆和集合直接单调队列解决

Cut the Sequence
Time Limit: 2000MS Memory Limit: 131072K
Total Submissions: 8123 Accepted: 2343

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

Hint

Use 64-bit integer type to hold M.

Source

令dp[i]表示前i个数按照题目要求的最小的和
则必然有dp[i] = min(dp[j] + max(dp[j +1 , a[j + 2].....a[i])) 
其中j<= i,j的位置还得满足题目中m 的限制
由于a数组都是大于0的,所以可以发现f必然是非递减的。
设dp[j + 1], dp[j + 2], ...dp[i]中值最大的下标为k
设x为[j + 1,k]的任意一个下标,则a[x],a[x+1],....a[i]的最大值的下标显然也是k了
由dp的非递减性,dp[j+1] + dp[k] <= dp[j+2]+a[k].....<= dp[k - 1] + a[k] 
很显然,我们只要取dp[j+1]+a[k]就可以了。
如何维护呢,可以联想到单调队列。
j不用从down(下界)一直枚举到i,我们可以枚举单调队列里的下标。
为什么呢。给出如下证明:
设单调队列里的队头下标为p.值为max,down<=p。那么在down和p之间的元素设为
down<a<b<c<p。那么根据dp的单调性可得dp[down]+max<=dp[a]+max<=dp[b]+max<=dp[c]+max
即下边界元素能保证最优。而j到i的最大值只能在单调队列q[]里取.所以只需遍历q[]里的值
即最大值的可选值就行了。对于每一个最大值的可选值其能产生的最优解为
dp[p]+q[j+1].val.p为上一最大值的下标。

维护一个递减的队列,存的是符合要求的某一段的最大值,

代码如下:

#include <iostream>
#include<string.h>
#include<stdio.h>
#define MIN(a,b)  ((a)<(b)?(a):(b))//第一次用感觉还不错
using namespace std;

struct node
{
    int val;//存单调队列的值
    int id;//存队列元素的下标
} q[100100];
int a[100100],head,tail,down;
int n;
__int64 dp[100100],sum,m;
int main()
{
    int i,j,p,flag;

    while(~scanf("%d%I64d",&n,&m))
    {
        for(i=1; i<=n; i++)
            scanf("%d",a+i);
        memset(dp,0,sizeof dp);
        sum=0;
        down=1;//down维护
        head=tail=0;
        flag=1;
        q[tail].val=-1;
        for(i=1; i<=n; i++)//计算以i结尾的最大值和的最小值
        {
            sum+=a[i];
            while(sum>m)//满足和不大于m的区间为low到i。做法很经典。
                sum-=a[down++];//减去前面的值使和满足条件。down为左边界。
            if(down>i)
            {
                flag=0;
                break;
            }
            while(tail>=head&&a[i]>q[tail].val)//将新元素入队
                tail--;
            q[++tail].id=i;
            q[tail].val=a[i];
            while(q[head].id<down)//剔除队列中超过范围的元素
                head++;
            dp[i]=dp[down-1]+q[head].val;
            for(j=head; j<tail; j++)
            {
                p=q[j].id;
                dp[i]=MIN(dp[i],dp[p]+q[j+1].val);//做法很经典!
            }
        }
        if(flag)
            printf("%I64d\n",dp[n]);
        else
            printf("-1\n");
    }
    return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值