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

题目描述

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


数据规模

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


思路

首先,这还是个很明显的划分类 D P DP
直接上状态: 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])}
但是这个状态已经是 O ( n 2 ) O(n^2) 级别的了
/-----------华---------丽-----------丽---------的-----------分---------割---------线-----------/
然后,我们会发现限制状态的最大因素就是 j j
整个转移中唯一与j有关的就是 j s “j * s”
因此,我们是否可以想办法将它去掉呢?
考虑在第i个任务结束后重新分一组,那么对第 i + 1 i + 1 ~ n n 个任务的影响就是加上了 ( s u m C [ n ] s u m C [ i ] ) s (sum_C[n] - sum_C[i]) * s
那么,我们就可以将j这维状态直接去掉
状态就变成了: 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


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


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


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


T i p s Tips :写读优的同志们千万别忘了:此题有负数!此题有负数!此题有负数!本人就 W A WA 了半个小时 Q A Q 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;
}```

猜你喜欢

转载自blog.csdn.net/qq_42883029/article/details/82431656