dp专辑 B - Batch Scheduling [ 斜率优化]

解题报告链接:http://hi.baidu.com/dou1121/blog/item/9de1b02a555038fde7cd403c.html 

题意:

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],那么我们可以分两个方面考虑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)即可转移。


复杂度分析: 由于任何一个点只能进出队列一次,所有均摊的复杂度为O(n)。


//Runtime Error CODE:  请大牛指点下,哪儿错了~~~

#include<iostream>
#include<cmath>
#include<algorithm>
#include<vector>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>


using namespace std;


const int maxn=11000;
__int64 sumT[maxn],sumC[maxn],f[maxn],m;
int que[maxn],n;


double Sploe(int j,int k)
{
    return (f[j]-f[k])/(sumT[j]-sumT[k]);
}


void init()
{
    int i;
    scanf("%I64d",&m);
    sumC[0]=sumT[0]=0;
    for(i=1; i<=n; i++)
        scanf("%I64d %I64d",&sumT[i],&sumC[i]);
    sumC[n+1]=sumT[n+1]=0;
    for(i=n; i>=1; i--)
    {
        sumT[i]=sumT[i+1]+sumT[i];
        sumC[i]=sumC[i+1]+sumC[i];
    }
}


void dp()
{
    int l,r;
    f[n+1]=0;
    l=1;
    r=1;
    que[1]=n+1;
    for(int i=n; i>=1; i--)
    {
        while( r>1 && Sploe(que[l],que[l+1]) < sumC[i] )
            l++;
        f[i]=f[que[l]]+(m+sumT[i]-sumT[que[l]])*sumC[i] ;
        while( r>1 && ( Sploe(que[r-1],que[r]) >= Sploe(que[r],i) ) )
            r--;
        que[++r]=i;
    }
    printf("%I64d\n",f[1]);
}


int main()
{
    while(scanf("%d",&n)!=EOF)
    {
        init();
        dp();
    }
    return 0;
}


//AC CODE :   终于找到上面代码哪儿错了 ,l写成1了


#include<iostream>
#include<cmath>
#include<algorithm>
#include<vector>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>

using namespace std;

const int maxn=11000;
__int64 sumT[maxn],sumC[maxn],f[maxn],m;
int que[maxn],n;

double Sploe(int j,int k)
{
    return (f[j]-f[k])/(sumT[j]-sumT[k]);
}

void init()
{
    int i;
    scanf("%I64d",&m);
    sumC[0]=sumT[0]=0;
    for(i=1; i<=n; i++)
        scanf("%I64d %I64d",&sumT[i],&sumC[i]);
    sumC[n+1]=sumT[n+1]=0;
    for(i=n; i>=1; i--)
    {
        sumT[i]=sumT[i+1]+sumT[i];
        sumC[i]=sumC[i+1]+sumC[i];
    }
}

void dp()
{
    int l,r;
    f[n+1]=0;
    l=1;
    r=1;
    que[1]=n+1;
    for(int i=n; i>=1; i--)
    {
        while( r>l && Sploe(que[l],que[l+1]) < sumC[i] )
            l++;
        f[i]=f[que[l]]+(m+sumT[i]-sumT[que[l]])*sumC[i] ;
        while( r>l && ( Sploe(que[r-1],que[r]) >= Sploe(que[r],i) ) )
            r--;
        que[++r]=i;
    }
    printf("%I64d\n",f[1]);
}

int main()
{
    while(scanf("%d",&n)!=EOF)
    {
        init();
        dp();
    }
    return 0;
}



你可能感兴趣的:(dp专辑 B - Batch Scheduling [ 斜率优化])