POJ3017——Cut the Sequence(单调队列+堆优化DP)

传送门:QAQQAQ

题意:给你一个数组,把它分成若干段,每一段之和都不得大于M,求每一段最大值之和的最小值

思路:状态转移方程:$dp[i]=min(dp[j]+max(a[j+1,i]))(sum_{k=j+1}^{k<=i}<=m)$

有关DP的优化,主要就是要缩短找到决策的时间

我们分析确定$i,j$时的决策,容易知道$dp$数组是单调不递减的,所以要使该决策是最佳决策,要么是所能取的最小$j$值,要么把$j,j+1$隔开时,后面的$max$有所减少

所以只有两种情况是最佳决策:一是最头,二是该数为该数后缀的最大值,对于第二种情况的决策我们可以用递减的单调队列来维护

因为最优决策不一定在队头,所以我们再用priority_queue维护一个堆,并用是否在单调队列内判断堆中值是否合法

在代码实现方面,因为STL堆不支持修改,删除操作,所以我们用懒惰删除法。

堆中维护的答案会随着区间最大值的变化而变化,但是我们发现对于当前插入的a[i]队列中比他小的都已经弹出,不合法,会被更新的只有一个——队列中$a[i]$的前一个值,只有它的max变化($j$后的最大值就是它在单调队列中的下一个$a[i]$值)

修改时,因为没法删除或修改,我们可以通过$bl$数组维护对于第$i$个切开最新的答案,再把最新答案插进去,这样当旧答案出来时不相等即为不合法,一删一插相当于修改

剩下的就是细节问题了(如循环最后插入$i$时的$max$取$a[i+1]$)

代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <string>
#include <queue>
#include <vector>
#define mk make_pair
using namespace std;
typedef long long ll;
typedef pair<ll,ll> pii;
const int N=100200;
const ll inf=(ll) 5e18;

deque<pii> q;//a[i],id
priority_queue<pii,vector<pii>,greater<pii> > Q;//value,id
ll n,m,a[N],bl[N],dp[N];

int main() 
{
    scanf("%lld%lld",&n,&m);
    for(int i=1;i<=n;i++) 
    {
        scanf("%lld",&a[i]);
        if(a[i]>m) {puts("-1"); return 0;}
    }
    ll now=0,left=1;
    for(int i=1;i<=n;i++)
    {
        now+=a[i];
        while(now>m&&left<=i) now-=a[left],left++;
        while(!q.empty()&&q.front().second<left) 
        {
            bl[q.front().second]=-1;
            q.pop_front();
        }
        while(!q.empty()&&q.back().first<=a[i]) //empty在前,否则RE 
        {
            bl[q.back().second]=-1;
            q.pop_back();
        }
        if(!q.empty()) 
        {
            pii pre=q.back();
            bl[pre.second]=dp[pre.second]+a[i];
            Q.push(mk(dp[pre.second]+a[i],pre.second));//删掉旧决策,插入新决策 
        }
        q.push_back(mk(a[i],i));
        dp[i]=dp[left-1]+q.front().first;//也可以这一段全选,不割最大值 
        while(!Q.empty())
        {
            pii tmp=Q.top(); 
            if(bl[tmp.second]!=tmp.first) 
            {
                Q.pop();
                continue;
            }
            dp[i]=min(dp[i],tmp.first); break; 
        }
        bl[i]=dp[i]+a[i+1];
        Q.push(mk(dp[i]+a[i+1],i));//dp值加上切掉以后下一段的最大值,即单调队列中后一个a[i]值 
    }
    cout<<dp[n]<<endl;
    return 0;
}
原文地址:https://www.cnblogs.com/Forever-666/p/13089143.html