[FROM WOJ](斜率优化DP基础)#3023 任务安排

#3023 任务安排

题面
N个任务排成一个序列在一台机器上等待完成(顺序不得改变),这N个任务被分成若干批,每批包含相邻的若干任务。从时刻0开始,这些任务被分批加工,第i个任务单独完成所需的时间是Ti。在每批任务开始前,机器需要启动时间S,而完成这批任务所需的时间是各个任务需要时间的总和(同一批任务将在同一时刻完成)。每个任务的费用是它的完成时刻乘以一个费用系数Fi。请确定一个分组方案,使得总费用最小。
例如:S=1;T={1,3,4,2,1};F={3,2,3,3,4}。如果分组方案是{1,2}、{3}、{4,5},则完成时间分别为{5,5,10,14,14},费用C={15,10,30,42,56},总费用就是153。

输入
第一行是N(1< =N< =5000)。
第二行是S(0< =S< =50)。
下面N行每行有一对数,分别为Ti和Fi,均为不大于100的正整数,表示第i个任务单独完成所需的时间是Ti及其费用系数Fi。

输出
一个数,最小的总费用。

样例输入
5
1
1 3
3 2
4 3
2 3
1 4

样例输出
153

提示
1<=N<=10000,1<=S<=50,1<=Ti,ci<=100

SOL
斜率优化的模板题之一。
最开始很容易想到一个二维的状态定义 f [ i ] [ j ] f[i][j] f[i][j]表示前 i i i个任务被分成 j j j批完成的最小费用,但是这样显然是 O ( N 3 ) O(N^3) O(N3)的。
于是想到去掉完成批数的那一维——因为你不需要知道当前做到了第几批,你只需要知道你再分出一批来对后继状态的影响。
于是有:
f [ i ] = m i n ( f [ i ] , f [ j ] + t [ i ] ∗ ( c [ i ] − c [ j ] ) + s ∗ ( c [ n ] − c [ j ] ) ) ( 0 &lt; = j &lt; i ) f[i]=min(f[i],f[j]+t[i]*(c[i]-c[j])+s*(c[n]-c[j])) (0&lt;=j&lt;i) f[i]=min(f[i],f[j]+t[i](c[i]c[j])+s(c[n]c[j]))(0<=j<i)
注意: t [ i ] , c [ i ] t[i],c[i] t[i],c[i]均为前缀和,不是题目描述的那种。
你将 [ j + 1 , i ] [j+1,i] [j+1,i]分为新的一批,会使后面的所有任务总共增加 s ∗ ( c [ n ] − c [ j ] ) s*(c[n]-c[j]) s(c[n]c[j])的费用。
于是算法被优化到了 O ( N 2 ) O(N^2) O(N2),可以过 N &lt; = 5000 N&lt;=5000 N<=5000的点。
还可不可以再优化呢?看到min我们容易去想用一个单调队列去维护,但是直接维护这个min显然不可行。
这时候我们如果抛开min,将整个式子看成一个函数,于是你可以发现两个变量—— f [ j ] , c [ j ] f[j],c[j] f[j],c[j]。使前者作为因变量,后者为自变量,则可以得到如下函数:
f [ j ] = ( s + t [ i ] ) ∗ c [ j ] + f [ i ] − t [ i ] ∗ c [ i ] − s ∗ c [ n ] f[j]=(s+t[i])*c[j]+f[i]-t[i]*c[i]-s*c[n] f[j]=(s+t[i])c[j]+f[i]t[i]c[i]sc[n]
这很像 y = k x + b y=kx+b y=kx+b,有斜率,有截距,而且截距最小的时候, f [ i ] f[i] f[i]最小。
于是考虑怎样使截距最小。
如果把 f [ j ] f[j] f[j]构成的点集表示在函数图像上,应该是这样的:
在这里插入图片描述
看上去很乱,但是如果你把相邻的三个点 j 1 , j 2 , j 3 j1,j2,j3 j1,j2,j3单独拿出来看:
在这里插入图片描述
于是可以知道,当 k ( j 1 , j 2 ) &lt; k ( j 2 , j 3 ) k(j1,j2)&lt;k(j2,j3) k(j1,j2)<k(j2,j3)时, j 2 j2 j2才是有效决策。
k ( x , y ) k(x,y) k(x,y)表示 x , y x,y x,y连线的斜率)
所以我们应该维护一个相邻两点连线斜率的单调递增队列,即一个下凸壳。
事实上就是找一个最接近那条直线的一个点。
在这里插入图片描述
我们只需要保留斜率大于k、单调递增的部分即可。
用一个单调队列维护,则这样做的效率是 O ( N ) O(N) O(N)的。

如果这样让你有些迷惑,不妨尝试用代数方法解释
k &gt; j k&gt;j k>j,且 k k k是一个相对更优的决策时,
f [ k ] + t [ i ] ∗ ( c [ i ] − c [ k ] ) + s ∗ ( c [ n ] − c [ k ] ) &lt; = f [ j ] + t [ i ] ∗ ( c [ i ] − c [ j ] ) + s ∗ ( c [ n ] − c [ j ] ) f[k]+t[i]*(c[i]-c[k])+s*(c[n]-c[k])&lt;=f[j]+t[i]*(c[i]-c[j])+s*(c[n]-c[j]) f[k]+t[i](c[i]c[k])+s(c[n]c[k])<=f[j]+t[i](c[i]c[j])+s(c[n]c[j])
那么化简可以得到:
s + t [ i ] &gt; = f [ k ] − f [ j ] / ( c [ k ] − c [ j ] ) s+t[i]&gt;=f[k]-f[j]/(c[k]-c[j]) s+t[i]>=f[k]f[j]/(c[k]c[j])
所以当某一决策的斜率为大于 s + t [ i ] s+t[i] s+t[i]的最小值,后面的决策都不能比它更优。

代码:

#include<bits/stdc++.h>
#define N 10005
#define int long long
using namespace std;
inline int rd(){
	int data=0,w=1;static char ch=0;ch=getchar();
	while(!isdigit(ch)&&ch!='-')ch=getchar();
	if(ch=='-')w=-1,ch=getchar();
	while(isdigit(ch))data=(data<<1)+(data<<3)+ch-'0',ch=getchar();
	return data*w;
}
int n,s,f[N],t[N],c[N],q[N];
signed main(){
	n=rd();s=rd();
	for(int register i=1;i<=n;i++){
		int register ti=rd(),ci=rd();
		t[i]=t[i-1]+ti;c[i]=c[i-1]+ci;
	}
	memset(f,0x5f,sizeof(f));f[0]=0;
	int l=1,r=1;q[l]=0;
	for(int register i=1;i<=n;i++){
		int k=s+t[i];
		while(l<r&&(f[q[l+1]]-f[q[l]])<=k*(c[q[l+1]]-c[q[l]]))l++;
		//维护斜率大于k 
		f[i]=f[q[l]]-k*c[q[l]]+t[i]*c[i]+s*c[n];
		while(l<r&&(f[q[r]]-f[q[r-1]])*(c[i]-c[q[r]])>=(f[i]-f[q[r]])*(c[q[r]]-c[q[r-1]]))r--;
		//维护下凸性质 
		q[++r]=i;
	}
	printf("%lld",f[n]);
	return 0;
}

Update:
如果ti可以为负数呢?
s+t[i]不能单调递增,因此此时维护的不只是大于k的部分,需要维护整个下凸壳,因此需要二分一下找到那个左边斜率小于k且右边斜率大于k的点。
二分代码:

inline int search(int i,int k){
	if(l==r)return q[l];
	int L=l,R=r;
	while(L<R){
		int mid=(L+R)>>1;
		if(f[q[mid+1]]-f[q[mid]]<=k*(c[q[mid+1]]-c[q[mid]]))L=mid+1;
		else R=mid;
	}
	return q[L];
}

题面详见WOJ #3622

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值