《算法竞赛进阶指南》0x5A斜率优化DP 任务安排的多种多种优化算法

题目链接:https://www.acwing.com/problem/content/description/302/

给出n个任务,每个任务需要T[i]的时间完成,在第j个时间完成的代价是C[i],任务分批完成,每一批的完成时间是一样的,每个批次都有一个S的启动机器的时间。

问完成这些任务所需要的最小代价。

经过对朴素算法的优化可以得到一个O(N^2)的算法,将对后续的影响(启动的时间)先加上,将一个“批次”维度优化掉。

代码:

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
typedef long long ll;
const int N = 5010;
ll f[N];
ll sumT[N],sumC[N];
int main(){
    int t,c;
    int n,S;
    cin>>n>>S;
    for(int i=1;i<=n;i++){
        cin>>t>>c;
        sumT[i]=sumT[i-1]+t;
        sumC[i]=sumC[i-1]+c;
    }
    
    memset(f,0x3f,sizeof(f));
    f[0]=0;
    for(int i=1;i<=n;i++){
        for(int j=0;j<i;j++){
            f[i]=min(f[i],f[j]+sumT[i]*(sumC[i]-sumC[j])+S*(sumC[n]-sumC[j]));
        }
    }
    
    cout<<f[n]<<endl;
} 

优化1:由于sumC[i]与sumT[i]具有单调性,所以通过线性规划方法可知相邻两点的斜率递增的情况下每个点才可能成为最优决策点,所以只需要通过一个单调队列维护斜率递增的边的两个端点即可。

注意最初的时候决策中的0需要加入。总的时间复杂度是O(N)。

代码:

#include<iostream>
#include<cstdio>
using namespace std;
const int N = 300010;
typedef long long ll;
ll f[N];
ll sumt[N],sumc[N];
int n,s;
int q[N];
int main(){
    cin>>n>>s;
    for(int i=1;i<=n;i++){
        int t,c;
        scanf("%d%d",&t,&c);
        sumt[i]=sumt[i-1]+t;
        sumc[i]=sumc[i-1]+c;
    }
    
    f[0]=0;
    int l=1,r=1;
    q[1]=0;//将(0,0)点加到决策中
     
    for(int i=1;i<=n;i++){
        while(l<r && (f[q[l+1]]-f[q[l]] <= 
            (s+sumt[i])*(sumc[q[l+1]]-sumc[q[l]])))l++;//排除一定选不到的决策
        if(l<=r)
            f[i]=f[q[l]]-(s+sumt[i])*sumc[q[l]]+
                sumt[i]*sumc[i]+s*sumc[n];
        while(l<r && (f[q[r]]-f[q[r-1]])*(sumc[i]-sumc[q[r]]) >=
                    (f[i]-f[q[r]])*(sumc[q[r]]-sumc[q[r-1]]))r--;
        q[++r]=i;
    } 
    
    cout<<f[n]<<endl;
}

优化2:当c[i]中存在负数的时候,每次加入一个i的时候斜率不再满足单调性,所以如果按照之前单调队列的做法,将队头中斜率小的删除,那么之后用一个找一个斜率小的直线的匹配点就会失败,所以之前斜率小的需要保留,这时候就需要维护一个完全凸壳。通过二分搜索一个合适的点满足左边的斜率小于k而且右边的斜率大于等于k。

在计算的时候用到了两个long long 相乘,所以需要变成__int64或者是double 再进行比较大小。

代码:

#include<iostream>
#include<cstdio>
using namespace std;
typedef long long ll;
const int N = 300010;
ll f[N];
int n,s;
ll sumc[N],sumt[N];
int q[N];
int l,r;
int binary_search(ll k){//搜索第一个大于k的斜率的位置 
    int L=l,R=r;
    if(l==r)return q[l];
    while(L<R){
        int mid=(L+R)>>1;
        if(f[q[mid+1]]-f[q[mid]] > (double)k*(sumc[q[mid+1]]-sumc[q[mid]]))R=mid;
        else L=mid+1;  
    }
    return q[L];
}
int main(){
    cin>>n>>s;
    for(int i=1;i<=n;i++){
        int t,c;
        scanf("%d%d",&t,&c);
        sumt[i]=sumt[i-1]+t;
        sumc[i]=sumc[i-1]+c;
    }
    
    f[0]=0;
    l=1;r=1;
    q[l]=0;
    for(int i=1;i<=n;i++){
        int p=binary_search(sumt[i]+s);//获得索引 
        f[i]=f[p]-(double)(s+sumt[i])*sumc[p]+
                sumt[i]*sumc[i]+s*sumc[n];
        while(l<r && (double)(f[q[r]]-f[q[r-1]])*(sumc[i]-sumc[q[r]]) >=
                    (double)(f[i]-f[q[r]])*(sumc[q[r]]-sumc[q[r-1]]))r--;
        q[++r]=i;
    }
    
    printf("%lld\n",f[n]);
    
    return 0;
}

 

posted @ 2020-08-04 10:32  WA自动机~  阅读(183)  评论(0编辑  收藏  举报