poj1180(斜率优化dp)

Batch Scheduling
Time Limit: 1000MS   Memory Limit: 10000K
Total Submissions: 3693   Accepted: 1708

Description

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 + (Tx + Tx+1 + ... + Tx+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.

Sample Input

5
1
1 3
3 2
4 3
2 3
1 4

Sample Output

153

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 <iostream>
#include <stdio.h>
#include <stdlib.h>
#include<string.h>
#include<algorithm>
#include<math.h>
#include<queue>
using namespace std;
typedef long long ll;
const int INF=1e9;
int sumt[10010],sumf[10010],dp[10010],dl[10010];///dl为队列数组
int main()
{
    int n;
    while(~scanf("%d",&n))
    {
        int c;
        scanf("%d",&c);
        for(int i=1; i<=n; i++)
            dp[i]=INF,scanf("%d%d",&sumt[i],&sumf[i]);
        dp[n+1]=sumt[n+1]=sumf[n+1]=0;
        for(int i=n; i>=1; i--)
            sumf[i]+=sumf[i+1],sumt[i]+=sumt[i+1];
        int l=0,r=0;
        dl[0]=n+1;
        for(int i=n; i>0; i--)
        {
            while(l<r && dp[dl[l+1]]-dp[dl[l]]<=(sumt[dl[l+1]]-sumt[dl[l]])*sumf[i])
                l++;
            dp[i]=dp[dl[l]]+(sumt[i]-sumt[dl[l]]+c)*sumf[i];
            while(l<r&&(dp[i]-dp[dl[r]])*(sumt[dl[r]]-sumt[dl[r-1]])<=(sumt[i]-sumt[dl[r]])*(dp[dl[r]]-dp[dl[r-1]]))
                r--;
            dl[++r]=i;
        }
        printf("%d\n",dp[1]);
    }
    return 0;
}

posted @ 2016-04-20 21:27  martinue  阅读(211)  评论(0编辑  收藏  举报