poj 1180 dp 斜率优化

【题意】

N个任务排成一个序列在一台机器上等待完成(顺序不得改变),这N个任务被分成若干批,每批包含相邻的若干任务。 从时刻0开始,这些任务被分批加工,第i个任务单独完成所需的时间是Ti。在每批任务开始前,机器需要启动时间S,而完成这批任务所需的时间是各个任务需 要时间的总和(同一批任务将在同一时刻完成)。每个任务的费用是它的完成时刻乘以一个费用系数Fi。请确定一个分组方案,使得总费用最小。(1 <= N <= 10000)

【题解】

S表示启动时间,T[i]是前i个任务的时间和,C[i]是前i个任务的开销和
f[i][j]=Min(f[i-1][k]+(S*i+T[j])*(c[j]-c[k]) ) ;
看了别人的结题报告,找到了优化到O(n*n)的方法。
就是从n往前推。
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
从后往前推效率可以降一维的原因:
正向思考,在前面的分块情况不清楚的时候是没法决定下一块的开销的,但是反过来,假设前面都没有分块,先算后面的开销,然后,如果前面要分块,后面的开销就会全部多出来的一个S,将这个S算进当前的分块开销里面,于是倒过来动态成为可能。

单调队列斜率优化分析:
考虑dp[i],对i<j<k来说,如果保证决策k不比决策j差的条件是:
dp[j]+(s+sumt[i]-sumt[j])*sumf[i]>=dp[k]+(s+sumt[i]-sumt[j])*sumf[i];
整理得:
(dp[k]-dp[j])/(sumt[k]-sumt[j])>=sumf[i];
设:
b[i,x]=dp[x]+(s+sumt[i]-sumt[x])*sumf[i];
g[k,j]=(dp[k]-dp[j])/(sumt[k]-sumt[j])
由上面的式子可知:
b[i,j]>=b[i,k]  <==>  g[k,j]>=sumf[i];   决策k不比决策j差
b[i,j]<b[i,k]  <==>  g[k,j]<sumf[i];    决策j比决策k 优
进而可知:
当i<c<b<a时,如果有g[a,b]>g[b,c],那么b永远不会成为dp[i]的决策。

证明:
如果g[a, b] > g[b, c],那么我们可以分两个方面考虑g[a, b]与sumF[i]的关系:
(1)如果g[a, b] >= sumF[i],那么决策a不会比决策b差,也就说决策b不可能是决策点
(2)如果g[a, b] < sumF[i],那么由于g[a, b] > g[b, c],那么g[b, c] < sumF[i],那么决策c要比决策b好,所以b还不能作为决策点 


根据上面的结论和一些特性,我们可以考虑维护一个斜率的队列来优化整个DP过程:

(1)假设a, b, c依次是队列尾部的元素,那么我们就要考虑g[a, b]是否大于g[b, c],如果g[a, b] > g[b, c],那么可以肯定b一定不会是决策点,所以我们可以从队列中将b去掉,然后依次向前推,直到找到一个队列元素少于3个或者g[a, b] <= g[b, c]的点才停止。
(2)假设a, b是依次是队列头部的元素,那么我们知道,如果g[a, b] < 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[a, b] >= sumF[i]。
(3)对于i的更新,一定是队列头部的决策点最好,所以O(1)即可转移。

【代码】

#include <iostream>
using namespace std;
const int maxn=10005;

long long dp[maxn];
int st[maxn],sf[maxn],t[maxn],f[maxn],q[maxn];
int n,s;

double cal(int x,int y)
{
       return double(dp[x]-dp[y])/double(st[x]-st[y]);
}

int main()
{
    freopen("pin.txt","r",stdin);
    freopen("pou.txt","w",stdout);
    int i,h,r,j;
    scanf("%d",&n);
    scanf("%d",&s);
    for (i=1;i<=n;i++)
        scanf("%d%d",&t[i],&f[i]);
    for (i=n;i;i--)
    {
        st[i]=st[i+1]+t[i];
        sf[i]=sf[i+1]+f[i];
    }
    h=1;r=0;
    dp[n]=(s+st[n])*sf[n];
    q[++r]=n;
    long long tt;
    for (i=n-1;i;i--)
    {
        while (r-h+1>=2 && cal(q[h],q[h+1])<sf[i]) h++;
        tt=s+st[i];
        tt*=sf[i];
        dp[i]=tt;
        j=q[h];
        tt=s+st[i]-st[j];
        tt*=sf[i];
        dp[i]=min(dp[i],dp[j]+tt);
        while (r-h>=1 && cal(q[r-1],q[r])>cal(q[r],i)) r--;
        q[++r]=i;
    }
    cout << dp[1] << endl;
    return 0;
}


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值