POJ1180 [IOI2002]任务安排(斜率优化dp入门好题)

upd:之前的代码是错的,已更正qwq,现在的珂以AC ——2019.8.21
我没想到以前的代码竟然那么丑……

题目描述

There is a sequence of N jobs to be processed on one machine. The jobs are numbered from 1 to N, so that the sequence is 1,2,…, N. The sequence of jobs must be partitioned into one or more batches, where each batch consists of consecutive jobs in the sequence. The processing starts at time 0. The batches are handled one by one starting from the first batch as follows. If a batch b contains jobs with smaller numbers than batch c, then batch b is handled before batch c. The jobs in a batch are processed successively on the machine. Immediately after all the jobs in a batch are processed, the machine outputs the results of all the jobs in that batch. The output time of a job j is the time when the batch containing j finishes.

A setup time S is needed to set up the machine for each batch. For each job i, we know its cost factor Fi and the time Ti required to process it. If a batch contains the jobs x, x+1,… , x+k, and starts at time t, then the output time of every job in that batch is t + S + (T x + T x+1 + … + T x+k). Note that the machine outputs the results of all jobs in a batch at the same time. If the output time of job i is Oi, its cost is Oi * Fi. For example, assume that there are 5 jobs, the setup time S = 1, (T1, T2, T3, T4, T5) = (1, 3, 4, 2, 1), and (F1, F2, F3, F4, F5) = (3, 2, 3, 3, 4). If the jobs are partitioned into three batches {1, 2}, {3}, {4, 5}, then the output times (O1, O2, O3, O4, O5) = (5, 5, 10, 14, 14) and the costs of the jobs are (15, 10, 30, 42, 56), respectively. The total cost for a partitioning is the sum of the costs of all jobs. The total cost for the example partitioning above is 153.

You are to write a program which, given the batch setup time and a sequence of jobs with their processing times and cost factors, computes the minimum possible total cost.

Input
Your program reads from standard input. The first line contains the number of jobs N, 1 <= N <= 10000. The second line contains the batch setup time S which is an integer, 0 <= S <= 50. The following N lines contain information about the jobs 1, 2,…, N in that order as follows. First on each of these lines is an integer Ti, 1 <= Ti <= 100, the processing time of the job. Following that, there is an integer Fi, 1 <= Fi <= 100, the cost factor of the job.
Output
Your program writes to standard output. The output contains one line, which contains one integer: the minimum possible total cost.

题目大意

一台计算机上有N个任务排成一个序列等待执行。现在要把这些任务分成若干批,每一批包含连续的一些任务,分别分批加工。执行第i个任务所需要的时间是Ti。执行每一批任务之前需要S的启动时间。

一个任务执行后,将等待至这一批所有任务都执行完才算完成。第i个任务的费用是它的完成时刻乘以Ci。请设计一个分组方案,使得总费用最小。

暴力方法

由于每一批任务连续,所以预处理出两个前缀和数组:

	sumt[i]	表示 执行前i个任务所需要的时间 , 即t[1]+t[2]+...+t[n]
	sumc[i]	表示 不乘时间时,执行前i个任务所需要的费用 , 即c[1]+c[2]+...+c[n]

dp子状态:

	dp[i][j] 表示 前i个任务分成j批所需要的最小费用。

于是可以由dp[k][j-1]推出dp[i][j]

	dp[i][j]=min{dp[k][j-1]+(s*j+sumt[i])*(sumc[i]-sumc[k])}

时间复杂度O(n^3),
空间复杂度O(n^2)。

略微优化

暴力方法的dp子状态空间是二维的,由于N <= 10000 ,所以考虑将二维状态降到一维。
将第一维去掉的想法不太实际,故考虑将第二维去掉。
首先思考所有任务都放到同一批中,观察每个任务需要等待S时间的次数:

	任务编号 1  2  3  ...  n
	等待次数 1  1  1  ...  1

当将1~pos1分成一个区间时,每个任务需要等待的次数变为:

	任务编号 1  2  3  ...  pos1  pos1+1  ...  n
	等待次数 1  1  1  ...   1      2     ...  2

(如果仍然看不出来,可以再分几次找找规律)
观察等待次数:每次多分出一个区间,区间左端点到n都需要多等待一次S时间。
故可以推出一个新的方程:

	dp[i]=min{dp[j]+sumt[i]*(sumc[i]-sumc[j]+s*(sumc[n]-sumc[j])}

斜率优化

当每次求dp[i]时,分别整理i,j的信息,把原来的方程进行玄学变形(移项):

	dp[i]=min{dp[j]-(s+sumt[i])*sumc[j]}+sumt[i]*sumc[i]+s*sumc[n]

去掉min函数,把dp[j]和sumc[j]看作变量,整理出dp[j]关于sumc[j]的一次函数(重要!):

	dp[j]=(s+sumt[i]) * sumc[j] + (dp[i]-sumt[i]*sumc[i]-s*sumc[n])
	  y  =    k       *    x    +                 b

(玄学的东西来了!)
其中sumc[j]是自变量,dp[j]是因变量,s+sumt[i]是斜率,后面一串为截距。
建立一个平面直角坐标系:
每个决策j的二元组(sumc[j] , dp[j])表示坐标系中的点。
当前状态dp[i]表示直线的截距(且这条直线斜率为s+sumt[i])。
令直线过每个点可以得到解出截距,使截距最小的就是最优决策。
如图:
在这里插入图片描述
讨论三个决策点j1,j2,j3(j1<j2<j3)对应的坐标系中的点:
在这里插入图片描述
第一种情况:上凸。用眼睛观察(hhh)可知,j2此时不可能成为最佳决策点,故放弃。
在这里插入图片描述
第二种情况:下凹。此时j2有可能成为最佳决策点。
最后把所有可能成为最佳决策点的j处理出来,即维护一个相邻点斜率单调递增的“凸壳”。
其中最佳决策点左端斜率<s+sum[i],右端斜率>s+sum[i](可以证明这样的点只有一个)。
又因为s+sumt[i]是单调递增的,所以可以用单调队列找第一个右端斜率>s+sum[i]的点。

总时间复杂度:循环递推O(n)+单调队列O(n)=O(n)。
空间复杂度:一维状态O(n)。

代码

#include<stdio.h>
#include<cstring>
#include<algorithm>
#define re register int
#define fast static int
using namespace std;
typedef long long ll;
int read() {
	int x=0,f=1;
	char ch=getchar();
	while(ch<'0' || ch>'9') {
		if(ch=='-')	f=-1;
		ch=getchar();
	}
	while(ch>='0' && ch<='9') {
		x=10*x+ch-'0';
		ch=getchar();
	}
	return x*f;
}
const int Size=10005;
int n,s,t[Size],c[Size];
ll sumt[Size],sumc[Size],dp[Size];
int Queue[Size];		//Queue:单调队列,维护一个下凸壳 
inline double X(int i) {
	return sumc[i];
}
inline double Y(int i) {
	return dp[i];
}
inline double slope(int i,int j) {
	return (Y(j)-Y(i))/(X(j)-X(i));
}
int main() {
	memset(dp,0x3f,sizeof(dp));
	n=read();
	s=read();
	for(re i=1; i<=n; i++) {
		t[i]=read();
		c[i]=read();
		sumt[i]=sumt[i-1]+t[i];
		sumc[i]=sumc[i-1]+c[i];
	}
	int hd=1,tl=0;
	Queue[++tl]=0;
	dp[0]=0;
	for(re i=1; i<=n; i++) {
		while(hd<tl && slope(Queue[hd],Queue[hd+1])<=s+sumt[i])	hd++;
		dp[i]=dp[Queue[hd]]-(s+sumt[i])*sumc[Queue[hd]]+sumt[i]*sumc[i]+s*sumc[n];
		while(hd<tl && slope(Queue[tl-1],Queue[tl])>slope(Queue[tl],i))	tl--;
		Queue[++tl]=i;
	}
	printf("%d",dp[n]);
	return 0;
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值