poj 1180 Batch Scheduling

题目链接地址http://poj.org/problem?id=1180

/*

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

题目分析:

       此道题目有个提示,即任务的顺序不得改变,那么很显然,任务的调度安排是具有阶段性的,我们可以用DP来解决此问题,这个DP方程不难写出,由于本题的DP方向有正推和倒推两种,我们选择倒推(至于为什么后面会讲解)

首先设:sumT[i]表示从i到n的任务所需要的时间总和,sumF[i]表示从i到n的费用系数总和,dp[i]表示对于从i到n的任务安排的最优解。

那么很容易可以得出这样一个简单的DP状态转移方程:     (注: 数组存储从1到n)

                dp[i] = min{dp[j] + (S + sumT[i] - sumT[j]) *sumF[i]        { i < j <= n + 1} 

                 边界条件dp[n+1] = 0

                 sumT[n+1]=sumF[n+1]=0

  可以清楚的看到,此种DP方法的时间复杂度是O(n^2)的,那么如何降低复杂度呢?

 

经典的优化分析:

        我们考虑在计算dp[i]时,对于i < j < k来说, 如果保证决策k比决策j大的条件是:

         dp[j] + (S + sumT[i] - sumT[j]) * sumF[i] < dp[k] + (S + sumT[i] -sumT[k]) * sumF[i]

 通过移项整理,可以化简为:

                         (dp[j] - dp[k]) / (sumT[j] - sumT[k]) < sumF[i]

 这个类似于斜率的东西又怎么来优化?

为了证明和更好的说明接下来的优化,我们定义这么几个变量:

设:  

                  d[i, x] = dp[x] + (S +sumT[i] - sumT[x]) * sumF[i] ;                

                  g[j, k] = (dp[j] - dp[k]) /(sumT[j] - sumT[k])

 那么我们可以总结一下上面推出来的式子:

根据上面有:

                               i<j<k

同理可证:             d[i, j] < d[i, k]    <=>  g[j, k] < sumF[i]         决策j比决策k小

 

进而我们发现这么一个问题,当i< j < k < l时,如果有g[j, k] < g[k, l],那么k永远都不会成为计算dp[i]时的决策点。

   这里我们可以再考虑一下只要g[j,k]<=g[k,l],那么k就不用作为i的决策点了。

 证明:

如果g[j, k] <g[k, l],那么我们可以分两个方面考虑g[j,k]与sumF[i]的关系:

(1)如果g[k, l] >= sumF[i],那么决策k大于等于决策l,也就说决策k不可能是决策点

(2)如果g[j, k] < sumF[i],那么决策k要大于决策j,所以k也不可能是决策点

 根据上面的结论和一些特性,我们可以考虑维护一个斜率的双向队列来优化整个DP过程:

 (1)假设i(马上要入队的元素)<j< k依次是队列尾部的元素,那么我们就要考虑g[i, j]是否大于g[j, k],如果g[i, j] < g[j, k],那么可以肯定j一定不会是决策点,所以我们可以从队列中将j去掉,然后依次向前推,直到找到一个队列元素少于3个或者g[i j]>= g[j, k]的点才停止。

(2)假设a>b(a是头元素)是依次是队列头部的元素,那么我们知道,如果g[b, a] < sumF[i]的话,那么对于i来说决策点b肯定优于决策点a,又由于sumF[i]是随着i减少而递增的(这个就是为什么倒推的原因),所以当g[a, b] < sumF[i]时,就一定有g[a, b] < sumF[i-1],因此当前的决策点a不仅仅在考虑dp[i]时不会是最佳决策点,而且在后面的DP中也一定不会是最佳决策点,所以我们可以把a从队列 的头部删除,依次往后如此操作,直到队列元素小于2或者g[b, a]>= sumF[i]。

(3)对于i的更新,一定是队列头部的决策点最好,所以O(1)即可转移。

 */

 

#include <cstdio>
#include <iostream>
#include <cstring>
#include <queue>

using namespace std;

const int MAXX=10005;

int dp[MAXX];
int sumt[MAXX], sumf[MAXX], que[MAXX];
int n, s, head, rear;

int SubDP(int j, int k){
	return dp[j] - dp[k];
}

int SubT(int j, int k){
	return sumt[j] - sumt[k];
}

int GetDp(int i, int j){
	return (sumt[i] - sumt[j] + s) * sumf[i] + dp[j];
}

void DP(){
	int i;
	dp[n+1] = sumt[n+1] = sumf[n+1] = 0; //这里为边界条件
	for(i = n; i >= 1; i--) sumt[i] += sumt[i+1];//sumt[i]表示从i到n的所有工作所用时间和
	for(i = n; i >= 1; i--) sumf[i] += sumf[i+1];//sumf[i]表示从i到n的所有工作花费系数的和
	head = 0;
	rear = -1;
	que[++rear]=n+1;

	for(i = n; i >= 1; i--){   //这里的等号我之所以加上是因为上面分析了其实等于的时候也可以去点head下面道理相同
		while(head < rear && SubDP(que[head+1],que[head]) <= SubT(que[head + 1], que[head]) * sumf[i]) head++;
		dp[i] = GetDp(i, que[head]);
		while(head < rear && SubDP(i, que[rear]) * SubT(que[rear], que[rear - 1])<= SubDP(que[rear], que[rear - 1]) * SubT(i, que[rear])) rear--;
		que[++rear] = i;
	}
	printf("%d\n", dp[1]);
}

int main(){
	while(scanf("%d%d", &n, &s)!=EOF){
		for(int i=1;i<=n;i++){
			scanf("%d%d", sumt+i, sumf+i);
		}
		DP();
	}
	return 0;
}

你可能感兴趣的:(优化,工作,存储,任务)