任务安排 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]);
}
posted @ 2019-11-13 20:42  fbz  阅读(211)  评论(0编辑  收藏  举报