任务安排(SDOI2012)斜率优化进阶

本文深入探讨了一种利用动态规划和单调队列优化技术来解决具有复杂约束条件的调度问题的方法。通过巧妙地定义状态转移方程和维护一个单调队列,作者成功地在大规模数据集上实现了高效的解决方案。文章详细解释了状态压缩动态规划的原理、关键步骤以及如何通过优化避免冗余计算,最终展示了该方法在实际应用中的高效性和实用性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目描述

N N N个任务排成一个序列在一台机器上等待完成(顺序不得改变),这 N N N个任务被分成若干批,每批包含相邻的若干任务。从时刻 0 0 0开始,这些任务被分批加工,第 i i i个任务单独完成所需的时间是 T i T_i Ti。在每批任务开始前,机器需要启动时间S,而完成这批任务所需的时间是各个任务需要时间的总和(同一批任务将在同一时刻完成)。每个任务的费用是它的完成时刻乘以一个费用系数 C i C_i Ci
请确定一个分组方案,使得总费用最小。


数据规模

1 ≤ N ≤ 3 ∗ 1 0 5 0 ≤ S , C ≤ 512 − 512 ≤ T ≤ 512 1≤N≤3*10^5 0≤S,C≤512 -512≤T≤512 1N31050S,C512512T512


思路

首先,这还是个很明显的划分类 D P DP DP
直接上状态: f [ i ] [ j ] f[i][j] f[i][j]表示前i个任务分成j批的最小代价。
易得 f [ i ] [ j ] = m i n f [ k ] [ j − 1 ] + ( j ∗ s + s u m T [ i ] ) ∗ ( s u m C [ i ] − s u m C [ k ] ) f[i][j] = min{f[k][j - 1] + (j * s + sum_T[i]) * (sum_C[i] - sum_C[k])} f[i][j]=minf[k][j1]+(js+sumT[i])(sumC[i]sumC[k])
但是这个状态已经是 O ( n 2 ) O(n^2) O(n2)级别的了
/-----------华---------丽-----------丽---------的-----------分---------割---------线-----------/
然后,我们会发现限制状态的最大因素就是 j j j
整个转移中唯一与j有关的就是 “ j ∗ s ” “j * s” js
因此,我们是否可以想办法将它去掉呢?
考虑在第i个任务结束后重新分一组,那么对第 i + 1 i + 1 i+1 ~ n n n 个任务的影响就是加上了 ( s u m C [ n ] − s u m C [ i ] ) ∗ s (sum_C[n] - sum_C[i]) * s (sumC[n]sumC[i])s
那么,我们就可以将j这维状态直接去掉
状态就变成了: f [ i ] f[i] f[i]表示前i个任务的最小代价
转移: f [ i ] = m i n f [ i ] , f [ j ] + ( s u m C [ i ] − s u m C [ j ] ) ∗ ( s + s u m T [ i ] ) + ( s u m C [ n ] − s u m C [ i ] ) ∗ s f[i] = min{f[i] , f[j] + (sum_C[i] - sum_C[j]) * (s + sum_T[i])} + (sum_C[n] - sum_C[i]) * s f[i]=minf[i],f[j]+(sumC[i]sumC[j])(s+sumT[i])+(sumC[n]sumC[i])s


-----------华---------丽-----------丽---------的-----------分---------割---------线-----------


然后,我们又发现,这是个非常经典的 1 D / 1 D 1D/1D 1D/1D动态规划
显然是无法通过 30 W 30W 30W级别的数据。
考虑优化,对于一道一维状态的 D P DP DP题,优化方向依然只有一个——转移。
(否则把状态优化了不就成了贪心吗?)
如何减少重复或不必要的枚举呢?
1 ≤ j 1 &lt; j 2 &lt; i 1 ≤ j1 &lt; j2 &lt; i 1j1<j2<i
则对于 i i i的决策, j 2 j_2 j2 j 1 j_1 j1优等价于满足
f [ j 2 ] + ( s u m C [ i ] − s u m C [ j 2 ] ) ∗ ( s + s u m T [ i ] ) &lt; f[j_2] + (sum_C[i] - sum_C[j_2]) * (s + sum_T[i]) &lt; f[j2]+(sumC[i]sumC[j2])(s+sumT[i])<
f [ j 1 ] + ( s u m C [ i ] − s u m C [ j 1 ] ) ∗ ( s + s u m T [ i ] ) f[j_1] + (sum_C[i] - sum_C[j_1]) * (s + sum_T[i]) f[j1]+(sumC[i]sumC[j1])(s+sumT[i])
简单的打开并整理过后就得到了
( f [ j 2 ] − f [ j 1 ] ) − s ∗ ( s u m C [ j 2 ] − s u m C [ j 1 ] ) (f[j_2]-f[j_1]) - s * (sum_C[j_2] - sum_C[j_1]) (f[j2]f[j1])s(sumC[j2]sumC[j1])
------------------------------------------------------------------------ &lt; s u m T [ i ] &lt; sum_T[i] <sumT[i]
s u m C [ j 2 ] − s u m C [ j 1 ] sum_C[j_2] - sum_C[j_1] sumC[j2]sumC[j1]


但这道题与 h d u 3057 hdu3057 hdu3057不同之处便是 s u m T [ i ] sum_T[i] sumT[i]不单调了
但是,仔细思考,这并没有破坏队尾维护的性质,即我们依然可以维护一个单调队列(实则是一个单调栈)
而区别就是现在取值的时候,要在这个单调序列中二分出一个斜率与 s u m T [ i ] sum_T[i] sumT[i]最为接近的值


T i p s Tips Tips:写读优的同志们千万别忘了:此题有负数!此题有负数!此题有负数!本人就 W A WA WA了半个小时 Q A Q QAQ QAQ
注:对斜率优化基础还不理解的可以参考斜率优化基础


代码

#include<cstdio>
using namespace std;
struct node
{
	long long t , c;
}
sum[300005];
long long f[300005];
int q[300005];
long long s;
inline int read()
{
	char ch = getchar();
	int flag = 1;
	while(ch < '0' || ch > '9')
	{
		if(ch == '-') flag = -1;
		ch = getchar();
	}
	int x = 0;
	while(ch >= '0' && ch <= '9') x = x * 10 + ch - 48 , ch = getchar();
	return x * flag;
}
inline bool cmp1(int j2 , int j1 , long long k)
{
	return f[j2] - f[j1] - s * (sum[j2].c - sum[j1].c) <= k * (sum[j2].c - sum[j1].c);
}
inline bool cmp2(int j3 , int j2 , int j1)
{
	return (f[j3] - f[j2] - s * (sum[j3].c - sum[j2].c)) * (sum[j2].c - sum[j1].c) <= (f[j2] - f[j1] - s * (sum[j2].c - sum[j1].c)) * (sum[j3].c - sum[j2].c);
}
inline int find(int l , int r , long long key)
{
	while(l < r)
	{
		int mid = (l + r) >> 1;
		if(cmp1(q[mid + 1] , q[mid] , key)) l = mid + 1;
		else r = mid;
	}
	return q[r];
}
int main()
{
	int n = read();
	s = read();
	for(int i = 1;i <= n;i++)
	{
		int t = read() , c = read();
		sum[i].t = sum[i - 1].t + t;
		sum[i].c = sum[i - 1].c + c;
	}
	int head = 1 , tail = 1;
	q[1] = 0;
	f[0] = 0;
	for(int i = 1;i <= n;i++)
	{
		int loc = find(head , tail , sum[i].t);
		f[i] = f[loc] + (s + sum[i].t) * (sum[i].c - sum[loc].c) + (sum[n].c - sum[i].c) * s;
		while(head < tail && cmp2(i , q[tail] , q[tail - 1])) tail--;
		q[++tail] = i;
	}
	printf("%lld\n",f[n]);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值