poj1180(斜率优化dp)
Time Limit: 1000MS | Memory Limit: 10000K | |
Total Submissions: 3693 | Accepted: 1708 |
Description
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
Output
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; }