pku 1180 Batch Scheduling 经典斜率优化

pku 1180 Batch Scheduling 经典斜率优化

题意:
N个任务,每个任务有两个参数,完成需要的时间t以及因子f,现在将这些任务分组,加工每个分组前机器需要冷却时间start,完成一组任务耗时为 t + Start + (Tx + Tx+1 + ... + Tx+k),问最少耗费的时间。
解法:
首先先解决这个问题,在这种分组数不确定的问题中,根据背包九讲中无限背包的策略,外层循环只要一层即可完成。
关键是内重循环还要枚举本组的起始位置,如果暴力的做就要N2了。
下面试图对初始的DP方程进行优化
考虑两种规划方向,第一种规划方向是从前到后
dp[i]=min{dp[k]+(dp[k]+sumt(i)-sumt(k)+start)*(sumf(i)-sumf(k))} 无比复杂的一个式子,推了N小时,在这个式子上下手似乎非常困难。。。(3个决策变量)
但是,如果换种思路,从后向前,将sumt以及sumf重新定义为最后一个任务到第i个任务的时间和和参数和,这样就简单多了。
dp[i]=min{dp[k]+(sumt(i)-sumt(k)+start)*sumf(i)}
然后很轻易能发现这个满足斜率优化的条件(2个待定决策变量,sumt(k)和dp(k))。
关于斜率优化,一般有代数和几何两种方法。先说几何方法
令g=dp[k]+(sumt(i)-sumt(k)+start)*sumf(i)},y=dp[k],x=sumt[k]
现在要使g最小
将式子整理下
y=sumf(i)*x+f-(start+sumt(i))*sumf(i)
将这个看做一个线性函数,目标要使得截距最小
再观察一下,斜率sumf(i)恒为正,且是随着i单调递减的,换句话说,是随着规划的过程单调递增的,而自变量x也是随着规划的过程单调递增的。我们画个图比划下
pku 1180 Batch Scheduling 经典斜率优化_第1张图片
首先,我们要维护的是一个上凹线,因为凹线内部的点不会被斜率为正的直线切到。再者,我们发现最优决策就在凹线的左端。我们来观察蓝色的线,假设这个是第i阶段的决策线,灰色凹线与蓝色线切点左侧的绿色部分显然是可以丢弃的。应为在切点处截距达到最小。并且在以后的决策中(浅蓝色的线),该段同样不会有任何作用。根据以上观察,我们可以使用栈队列(很多人简称为单调队列)的数据结构。这个东西网上介绍的比较多,我就不细说了。
第二种方法就是代数法。
我看网上有一个牛写的不错,就贴过来吧- -

引自http://hi.baidu.com/wangkun_zhen/blog/item/5e85d0cc4fb887590fb345dd.html     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)即可转移。


初始化的时候要注意, 必须在n+1的位置处加一个虚拟的状态,不能将第一个状态(即i=n的状态)作为初始状态,因为可能将所有任务仅分为一段。
代码:

 

 1 # include  < stdio.h >
 2 # define N  10005
 3 int  n,start,st[N],sf[N];
 4 int  q[N],s =- 1 ,e = 0 ;
 5 int  dp[N];
 6 int  cmp( const   int  a, const   int  b, const   int  k)
 7 {
 8    if(dp[b]-dp[a]<(long long)k*(st[b]-st[a])) return -1;
 9    else if((long long)dp[b]-dp[a]==(long long)k*(st[b]-st[a])) return 0;
10    else return 1;
11}

12 int  cmp1( const   int  y1, const   int  x1, const   int  y2, const   int  x2, const   int  y3, const   int  x3)
13 {
14    if(((long long)y3-y2)*(x2-x1)<((long long)y2-y1)*(x3-x2)) return -1;
15    else if(((long long)y3-y2)*(x2-x1)==((long long)y2-y1)*(x3-x2)) return 0;
16    else return 1;
17}

18 int  main()
19 {
20    int i;
21    scanf("%d%d",&n,&start);
22    for(i=0;i<n;i++)
23        scanf("%d%d",st+i,sf+i);
24    for(i=n-2;i>=0;i--)
25    {
26        st[i]+=st[i+1];
27        sf[i]+=sf[i+1];
28    }

29    q[e]=n;
30    dp[n]=0;
31    for(i=n-1;i>=0;i--)
32    {
33       while(e>=s+2&&cmp(q[s+1],q[s+2],sf[i])!=1) s++;
34       dp[i]=dp[q[s+1]]+(start+st[i]-st[q[s+1]])*sf[i];
35       while(e>=s+2&&cmp1(dp[q[e-1]],st[q[e-1]],dp[q[e]],st[q[e]],dp[i],st[i])!=1) e--;
36       q[++e]=i;
37    }

38    printf("%d\n",dp[0]);
39    return 0;
40}

41

你可能感兴趣的:(pku 1180 Batch Scheduling 经典斜率优化)