P2365 任务安排 (斜率优化dp入门)

题目链接:点击这里

题目大意:
n n n 个任务排成一个序列在一台机器上等待完成(顺序不得改变),这 n n n 个任务被分成若干批,每批包含相邻的若干任务。
从零时刻开始,这些任务被分批加工,第 i i i 个任务单独完成所需的时间为 t i t_i ti
在每批任务开始前,机器需要启动时间 s s s,而完成这批任务所需的时间是各个任务需要时间的总和(同一批任务将在同一时刻完成)。
每个任务的费用是它的完成时刻乘以一个费用系数 f i f_i fi 。请确定一个分组方案,使得总费用最小。

题目分析:
很容易可以发现状态 d p [ i ] dp[i] dp[i] 表示前 i i i 个物品完成的最小花费
其状态转移方程为 d p [ i ] = m i n j = 0 i − 1 ( d p [ j ] + s u m t [ i ] ∗ ( s u m c [ i ] − s u m c [ j ] ) + s ∗ ( s u m c [ n ] − s u m c [ j ] ) ) dp[i] = min_{j=0}^{i-1}(dp[j]+sumt[i]*(sumc[i]-sumc[j])+s*(sumc[n]-sumc[j])) dp[i]=minj=0i1(dp[j]+sumt[i](sumc[i]sumc[j])+s(sumc[n]sumc[j]))
其中 s u m c [ i ] = ∑ k = 1 i c [ i ] sumc[i]=\sum_{k=1}^ic[i] sumc[i]=k=1ic[i](费用前缀和), s u m t ∑ k = 1 i t [ i ] sumt\sum_{k=1}^it[i] sumtk=1it[i] (时间前缀和)
显然这个方程的时间复杂度是 O ( n 2 ) O(n^2) O(n2) 的,是可以通过此题的
但是斜率优化可以继续优化该方程的时间复杂度:
G [ i , j ] G[i,j] G[i,j] d p [ i ] dp[i] dp[i] 的一个决策点,即 G [ i , j ] = d p [ j ] + s u m t [ i ] ∗ ( s u m c [ i ] − s u m c [ j ] ) + s ∗ ( s u m c [ n ] − s u m c [ j ] ) G[i,j]=dp[j]+sumt[i]*(sumc[i]-sumc[j])+s*(sumc[n]-sumc[j]) G[i,j]=dp[j]+sumt[i](sumc[i]sumc[j])+s(sumc[n]sumc[j])
a , b a,b a,b 为为 d p [ i ] dp[i] dp[i] 的两个决策点且 b b b 点比 a a a 点更优(不妨令 a < b a<b a<b),则有 G [ i , a ] > G [ i , b ] G[i,a]>G[i,b] G[i,a]>G[i,b]
d p [ a ] + s u m t [ i ] ∗ ( s u m c [ i ] − s u m c [ a ] ) + s ∗ ( s u m c [ n ] − s u m c [ a ] ) > d p [ b ] + s u m t [ i ] ∗ ( s u m c [ i ] − s u m c [ b ] ) + s ∗ ( s u m c [ n ] − s u m c [ b ] ) dp[a]+sumt[i]*(sumc[i]-sumc[a])+s*(sumc[n]-sumc[a]) > dp[b]+sumt[i]*(sumc[i]-sumc[b])+s*(sumc[n]-sumc[b]) dp[a]+sumt[i](sumc[i]sumc[a])+s(sumc[n]sumc[a])>dp[b]+sumt[i](sumc[i]sumc[b])+s(sumc[n]sumc[b])
等价于 d p [ a ] − s u m t [ i ] ∗ s u m c [ a ] − s ∗ s u m c [ a ] > d p [ b ] − s u m t [ i ] ∗ s u m c [ b ] − s ∗ s u m c [ b ] dp[a]-sumt[i]*sumc[a]-s*sumc[a] > dp[b]-sumt[i]*sumc[b]-s*sumc[b] dp[a]sumt[i]sumc[a]ssumc[a]>dp[b]sumt[i]sumc[b]ssumc[b]
等价于 ( s u m t [ i ] + s ) ∗ ( s u m c [ b ] − s u m c [ a ] ) > d p [ b ] − d p [ a ] (sumt[i]+s)*(sumc[b]-sumc[a]) > dp[b]-dp[a] (sumt[i]+s)(sumc[b]sumc[a])>dp[b]dp[a]
等价于 s u m t [ i ] + s > d p [ b ] − d p [ a ] s u m c [ b ] − s u m c [ a ] sumt[i]+s>\frac{dp[b]-dp[a]}{sumc[b]-sumc[a]} sumt[i]+s>sumc[b]sumc[a]dp[b]dp[a] (因为 s u m c sumc sumc 是前缀和故有 s u m c [ b ] − s u m c [ a ] > 0 sumc[b]-sumc[a]>0 sumc[b]sumc[a]>0
我们发现 s u m t [ i ] sumt[i] sumt[i] 是一个关于 i i i 的单调递增的函数,所以如果有一个点的价值比其后面的点的价值更差,那么这个点一定不会被选择
我们加入一个新的点 c c c a < c < b a<c<b a<c<b)且 k a b > k a c > k b c k_{ab} > k{ac}>k{bc} kab>kac>kbc c c c 点一定不会被选择,即一定不会是决策点,因为当 a a a 不是决策点时,说明 k a b k_{ab} kab 已经不满足条件了,而 k b c k_{bc} kbc 更小就更不满足条件了,其实我们通过最后的推出的式子就可以发现,可能成为决策点的点一定是斜率不断增加的点,其构成了一个凸包,所以我们通过单点队列维护一个凸包把每一个可能成为决策点的点加进去,队首元素即为当前的最优决策点
时间复杂度 O ( n ) O(n) O(n)
具体细见代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#define ll long long
#define inf 0x3f3f3f3f
using namespace std;
int read()
{
	int res = 0,flag = 1;
	char ch = getchar();
	while(ch<'0' || ch>'9')
	{
		if(ch == '-') flag = -1;
		ch = getchar();
	}
	while(ch>='0' && ch<='9')
	{
		res = (res<<3)+(res<<1)+(ch^48);//res*10+ch-'0';
		ch = getchar();
	}
	return res*flag;
}
const int maxn = 1e5+5;
const int mod = 1e9+7;
const double pi = acos(-1);
const double eps = 1e-8;
int sumc[maxn],sumt[maxn];
int n,s,dp[maxn],qu[maxn],l,r;
double calc(int i,int j)
{
	return 1.0*(dp[i]-dp[j])/(sumc[i]-sumc[j]);
}
int main()
{
	n = read(),s = read();
	for(int i = 1;i <= n;i++)
	{
		int t = read(),c = read();
		sumt[i] = sumt[i-1]+t;
		sumc[i] = sumc[i-1]+c;
	}
	l = r = 1;
	for(int i = 1;i <= n;i++)
	{
		while(l < r && calc(qu[l],qu[l+1]) < sumt[i]+s) l++;
		dp[i] = dp[qu[l]]+sumt[i]*(sumc[i]-sumc[qu[l]])+s*(sumc[n]-sumc[qu[l]]);
		while(l < r && calc(qu[r-1],qu[r]) > calc(qu[r],i)) r--;
		qu[++r] = i;
	}
	printf("%d\n",dp[n]); 
	return 0;
}

经验:
斜率优化 d p dp dp 的有一个较为明显的特征那就是每一个决策点都可以用一个坐标来表示,点的优劣性取决于他们之间的斜率,因此可以通过单调队列维护该点集,来在 d p dp dp 过程中减少以为的复杂度

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值