【BZOJ 2726】任务安排【斜率优化dp】

题意:

       n个需要被处理的任务,机器启动时间为s。每个任务都有时间Ti和花费Ci,计算方法为完成这批任务所需的时间是各个任务需要时间的总和。注意,同一批任务将在同一时刻完成,新的一批任务开始时,机器需要重新启动。确定一个分组方案,使得总费用最小。【每批任务包含相邻的若干任务】

 

思路:

       1  <= n <= 3*1e5,0 <= S,Ci <= 512,-512 <= Ti <= 512。

       首先我们需要列出dp方程,dp[i]表示完成前i个任务的最小费用。此处由于涉及到s,因此我们需要用到一个叫做“费用提前计算”的思想。因为s对于后续的每个任务都有影响,因此我们应当将s对后续任务的影响提前计算。

       假设dp[i]由dp[j]更新而来,则dp[i] = dp[j] + sumT[i]*(sumC[i]-sumC[j]) + s*(sumC[N]-sumC[j]); 将转移方程拆开,则可以得到dp[j] - s*sumC[j] = sumT[i]*sumC[j]+dp[i]-s*sumC[N]-sumT[i]*sumC[i],由此我们可以发现令横坐标x = sumC[j],纵坐标y = dp[j]-s*sumC[j],则对于每一个j,都可以在平面中确定一个(x,y)坐标,而直线的斜率也是固定的,为sumT[i]。

       因此在平面中的这么多点中,我们需要维护一个下凸壳,更新dp[i]的时候,只需要在下凸壳中,二分x点与x+1点的斜率是否小于sumT[i],找到一个点,该点左边的斜率小于k,右边斜率大于k,则该点即为该下凸壳中的最优点。

       再谈一下如何维护下凸壳,用一个数组,末尾为qt,则判断qt-1与x的斜率是否小于等于qt-1与qt的斜率,如果小于等于,则需要弹出qt,然后不断继续往下更新。注意此处如果是等于也需要弹出。

       本题在维护凸壳时,可能会爆long long,需要转成long double。

 

代码:

#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#define rep(i,a,b) for(int i = a; i <= b; i++)
using namespace std;
const int N = 1e6+100;
typedef long long ll;

int n,qt;
ll s,T[N],C[N],sumT[N],sumC[N],dp[N],q[N],xx[N],yy[N];

void calc(int x)	//在凸壳中二分最优点,斜率优化
{
	int l = 1, r = qt-1, ans = q[l];
	ll k = sumT[x];
	while(l <= r)
	{
		int mid = (l+r)>>1;
		if((long double)(yy[q[mid+1]]-yy[q[mid]]) <= (long double)(xx[q[mid+1]]-xx[q[mid]])*(long double)k)
			ans = q[mid+1], l = mid+1;
		else r = mid-1;
	}
	dp[x] = yy[ans]-k*xx[ans]+s*sumC[n]+sumT[x]*sumC[x];
}

void solve()
{
	qt = 1, q[1] = 0;
	xx[0] = yy[0] = 0;
	rep(i,0,n) xx[i] = 0, yy[i] = 0;
	rep(i,1,n)
	{
		calc(i);
		xx[i] = sumC[i], yy[i] = dp[i]-s*sumC[i];
		while(qt >= 2){
			//重点在于 >=,> 会wa
			if((long double)(yy[q[qt]]-yy[q[qt-1]])*(long double)(xx[i]-xx[q[qt-1]]) >= (long double)(yy[i]-yy[q[qt-1]])*(long double)(xx[q[qt]]-xx[q[qt-1]]))
				qt--;
			else break;
		}
		qt++; q[qt] = i;
	} 
	printf("%lld\n",dp[n]);
}

int main()
{
	while(~scanf("%d%lld",&n,&s))
	{
		sumT[0] = sumC[0] = 0;
		rep(i,1,n){
			scanf("%lld%lld",&T[i],&C[i]);
			sumT[i] = sumT[i-1]+T[i];
			sumC[i] = sumC[i-1]+C[i];
		}
		solve();
	}
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Gene_INNOCENT

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值