POJ-3017 Cut the Sequence(单调队列+DP+尺取)

题意

给定一个长度为 n 的序列,把序列分成若干段。每段的和不能超过 M 。定义每段的价值为这段的最大值,求所有段价值总和最小是多少。
1 n 10 5

思路

做这种单调队列优化 d p 的题目,首先还是得列出暴力的 d p 方程,设 d p i [ 1 , i ] 的最小价值,不难得到:
d p i = m i n { d p j 1 + m a x { a j . . . a i } } k = j i a k M
然后尝试用单调队列优化。首先 j 的最小值通过尺取确定。其次,发现如果按顺序扫的时候出现了一个较大的 a i ,那么在 a i 前比它小的数下标对应的 d p 值都用不到了,应为完全可以通过拉大上一个区间,取到最后一个比 a i 大的数上。所以我们只用维护一个关于 a 的递减的单调队列,每次转移只从单调队列里转移即可。要注意特判单调队列里的队首元素。

代码

#include<iostream>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#define FOR(i,x,y) for(int i=(x);i<=(y);i++)
#define DOR(i,x,y) for(int i=(x);i>=(y);i--)
#define N 100003
typedef long long LL;
using namespace std;
int a[N];LL dp[N];

bool cmp(int x,int y){return x>y;}
template<int *a>struct monoque
{
    int q[N],L,R;
    monoque(){L=1,R=0;}
    void clear(){L=1,R=0;}
    void del(int _){while(L<=R&&!cmp(a[q[R]],a[_]))R--;}
    void push(int _){q[++R]=_;}
    int front(){return q[L];}
    int rear(){return q[R];}
    void pop(int _){while(L<=R&&_>=q[L])L++;}
};
monoque<a>mq;

int main()
{
    int n;LL m;
    scanf("%d%lld",&n,&m);
    FOR(i,1,n)
    {
        scanf("%d",&a[i]);
        if(a[i]>m)
        {
            printf("-1\n");
            return 0;
        }
    }
    LL sum=0;
    int L=1;
    FOR(R,1,n)
    {
        sum+=a[R];
        mq.del(R);
        mq.push(R);
        while(L<=R&&sum>m)sum-=a[L++];
        mq.pop(L-1);
        dp[R]=dp[L-1]+a[mq.front()];
        FOR(i,mq.L+1,mq.R)dp[R]=min(dp[R],dp[mq.q[i-1]]+a[mq.q[i]]);
    }
    printf("%lld\n",dp[n]);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/Paulliant/article/details/81168300
cut