Tyvj1098任务安排

这篇讲解部分已经整合进入http://blog.csdn.net/OIljt12138/article/details/51052195 。代码留在这里。

题目

描述

N个任务排成一个序列在一台机器上等待完成(顺序不得改变),这N个任务被分成若干批,每批包含相邻的若干任务。从时刻0开始,这些任务被分批加工,第i个任务单独完成所需的时间是Ti。在每批任务开始前,机器需要启动时间S,而完成这批任务所需的时间是各个任务需要时间的总和(同一批任务将在同一时刻完成)。每个任务的费用是它的完成时刻乘以一个费用系数Fi。请确定一个分组方案,使得总费用最小。
例如:S=1;T={1,3,4,2,1};F={3,2,3,3,4}。如果分组方案是{1,2}、{3}、{4,5},则完成时间分别为{5,5,10,14,14},费用C={15,10,30,42,56},总费用就是153。

输入格式

  • 第一行是N(1<=N<=5000)。
  • 第二行是S(0<=S<=50)。
  • 下面N行每行有一对数,分别为Ti和Fi,均为不大于100的正整数,表示第i个任务单独完成所需的时间是Ti及其费用系数Fi。

输出格式

  • 一个数,最小的总费用。

测试样例

  • 输入
5 
1 
1 3 
3 2 
4 3 
2 3 
1 4
  • 输出
153

分析

很显然,由于顺序不能改变,所以可以使用dp来求解。状态是关键!下面给出两个不同的方法,由于状态不同,复杂度相去甚远。
虽然时间就是金钱,但是这里我们会把F值看作金钱,而时间看成金钱的单位。

费用S = T * F,显然S = F+F+F...(TF)。
所以时间T每增加k,即T' = T+k,
           那么S' - S = kF。
我们便说:金钱又被收了k次。

统一收取:2D/1D方程

这是一种最显然的方法,即记录下当前时间,在决策时直接使用Sigma(Time) * F获取当前费用。不妨用dp[i][t]表示从t秒开始,完成任务i到n所需最小费用。不难得出dp方程:

dp[i][t] = min{dp[k+1][t+T[i..k]]+(t+T[i..k]*F[k])}
k = i..n
边界:dp[n+1][..] = 0
目标:dp[1][0]

这里一定要理解“统一收取”的意义。事实上,当前使用的时间将会导致未来消费的增加(这显然违反无后效性)。统一收取本质上是增加一维来记录当前时间这个会被改变的状态,从而满足无后效性。
但是显然这一维的增加是冗余的,而且使时间/空间复杂度达到了无法接受的程度。能不能在每次产生时间改变时(或者形象的,消费时)就计算出在将来会造成的费用呢?

立刻支付:1D/1D方程

顺着刚才的思路,我们考虑每当一个决策可能在未来产生消费时,就立刻预先支付这个价值。建议各位先深刻理解刘汝佳/黄亮黑书P151的最优二分检索树是如何推算出dp方程的。
分析问题: 每个任务的费用是它的完成时刻乘以一个费用系数Fi。不妨看成费用系数Fi进行了连续加法,当前决策之前的每一个决策每使时间过去了1,就要在当前费用中加上一个Fi;反过来,当前的决策每使时间增加k分钟,或者如开头所说收了k次钱,就会使它以及其后的每一个任务的费用加上Fi。正是这种化乘法为加法的思路,削去了冗余的时间一维。由此得出dp方程:

dp[i]表示第i个到第n个任务所需的费用
dp[i] = min{dp[j+1] + (T[i..j]+S) * F[i..n]}
其中 ij ≤ n
边界是 dp[n+1]=0
目标是 dp[1]

深刻理解这个方程,会受益匪浅。

解决

方程中可以看出,i是递减的,无脑给出c++代码

#include <iostream>
#include <cstring>
using namespace std;

#define ll int64_t
ll dp[5005];
ll sumT[5005],sumF[5005];
int n,s;

int main() {
    memset(dp,127,sizeof dp);
    cin >> n >> s;
    dp[n+1] = 0;
    int x,y;
    sumT[0] = sumF[0] = 0;
    for (int i=1; i<=n; i++) {
        cin >> x >> y;
        sumT[i] = sumT[i-1]+x;
        sumF[i] = sumF[i-1]+y;
    }
    for (int i=n; i>=1; i--) {
        for (int j=i; j<=n; j++) {
            dp[i] = min(dp[i], dp[j+1]+(s+sumT[j]-sumT[i-1])*(sumF[n]-sumF[i-1]));
        }
    }
    cout << dp[1] << endl;
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值