任务安排 2

https://loj.ac/problem/10185

题目描述

  有\(N\)个任务,每个任务有一定的完成时间\(T_i\)和费用系数\(C_i\),每一批任务完成的时间为启动时间\(S\)加上完成的总时间,费用这个任务为所在批次的完成时间乘上它的费用系数,求最小代价。

思路

  比较容易得到一个\(N^3\)\(dp\),我们考虑暴力枚举任务和批次,令\(f[i][j]\)表示前\(i\)个任务分成\(j\)批的最小代价,在预处理完前缀和之后,那么\(f[i][j]=min\{f[k][j-1]+(j*S+sumT[j]-sumT[k])*(sumC[j]-sumC[k])\}\),直接求即可。

  考虑前一个做法中批次并没有关键的用途,我们需要的仅仅是机器的启动次数,用滚动数组优化空间后,考虑到每次启动都会对后续有影响,我们可以直接计算这个费用。令\(f[i]\)表示前\(i\)个任务的最小花费(包括后续影响),那么\(f[i]=min\{f[j]+sumT[i]*(sumC[i]-sumC[j])+S*(sumC[n]-sumC[j])\}\),复杂度为\(N^2\)

  我们再进行优化,去掉小括号,并化简,可得:
\[ f[j]=(sumT[i]+S)sumC[j]+f[i]-sumT[i]*sumC[i]-S*sumC[n] \]

  这样我们就可以看做一个与\(j\)相关的一次函数,每个点为\((sumC[j],f[j])\),对于当前的\(i\),由于除\(f[i]\)都是确定的,我们只要让直线的截距最小即可,这个可以通过维护下凸壳来实现,复杂度为\(O(N)\)

代码

#include<bits/stdc++.h>
using namespace std;
const int N=1e4+10;

int read()
{
    int res=0,w=1;
    char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){res=(res<<3)+(res<<1)+(ch^48);ch=getchar();}
    return res*w;
}

int sumt[N],sumc[N],f[N],q[N];
int main()
{
    int n=read(),s=read();
    for(int i=1;i<=n;i++)
        sumt[i]=sumt[i-1]+read(),
        sumc[i]=sumc[i-1]+read();
    int l=0,r=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++;
        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[i]<<endl;
    }
    printf("%d\n",f[n]);
}

猜你喜欢

转载自www.cnblogs.com/fangbozhen/p/11853045.html