您的位置:首页 > 其它

dp 斜率优化 poj1180(转) 反过来dp 经典

2012-07-22 18:26 218 查看
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)。
//coded by huicpc0207
#include<iostream>
#include<stdio.h>
#include<math.h>
#include<string.h>
#include<string>
#include<vector>
#include<stack>
#include<queue>
#include<algorithm>
#include<set>
#include<map>
using namespace std;
typedef long long LL;
const int N=10050;
int n,s,t
,f
,id
;
LL opt
,sf
,st
;
inline LL dx(int k1,int k2){ return st[k1]-st[k2]; }
inline LL dy(int k1,int k2){ return opt[k1]-opt[k2]; }
inline bool G(int k1,int k2,int i)
{
return dy(k1,k2)>sf[i]*dx(k1,k2);
}
inline bool inc(int k1,int k2,int k3)
{
return dy(k1,k2)*dx(k2,k3)<dy(k2,k3)*dx(k1,k2);
}
int main()
{
while(scanf("%d%d",&n,&s)!=-1)
{
for(int i=n;i>=1;i--)
scanf("%d%d",&t[i],&f[i]);
st[0]=sf[0]=0;
for(int i=1;i<=n;i++)
{
st[i]=st[i-1]+t[i];
sf[i]=sf[i-1]+f[i];
}
int l=0,r=-1;
id[++r]=0; opt[0]=0;
for(int i=1;i<=n;i++)
{
while(l<r&&G(id[l],id[l+1],i)) l++;
int k=id[l];
opt[i]=opt[k]+(s+st[i]-st[k])*sf[i];
while(l<r&&!inc(id[r-1],id[r],i)) r--;
id[++r]=i;
}
printf("%I64d\n",opt
);
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: