【日常刷题】费用提前&斜率优化DP:[IOI2002]任务安排

任务安排

Description
There is a sequence of N jobs to be processed on one machine. The jobs are numbered from 1 to N, so that the sequence is 1,2,…, N. The sequence of jobs must be partitioned into one or more batches, where each batch consists of consecutive jobs in the sequence. The processing starts at time 0. The batches are handled one by one starting from the first batch as follows. If a batch b contains jobs with smaller numbers than batch c, then batch b is handled before batch c. The jobs in a batch are processed successively on the machine. Immediately after all the jobs in a batch are processed, the machine outputs the results of all the jobs in that batch. The output time of a job j is the time when the batch containing j finishes.

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.

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

例如: S = 1 ; T = 1 , 3 , 4 , 2 , 1 ; F = 3 , 2 , 3 , 3 , 4 S=1;T={1,3,4,2,1};F={3,2,3,3,4} S=1T=1,3,4,2,1F=3,2,3,3,4。如果分组方案是 1 , 2 、 3 、 4 , 5 {1,2}、{3}、{4,5} 1,234,5,则完成时间分别为 5 , 5 , 10 , 14 , 14 {5,5,10,14,14} 5,5,10,14,14,费用 C = 15 , 10 , 30 , 42 , 56 C={15,10,30,42,56} C=15,10,30,42,56,总费用就是 153 153 153

S o l u t i o n Solution Solution

O ( n 2 ) O(n^2) O(n2)算法

如果考虑O(n^2)的算法,我们如果不考虑S这一个值对最后结果的影响:

状态:设f[i]表示前i个机器分成若干组以后的最小花费。

状态转移,就是枚举与当前机器i分组的临界点j,状态转移方程如下:
f [ i ] = m i n ( f [ j ] + s u m t [ i ] ∗ ( s u m f [ i ] − s u m f [ j ] )    ) f[i]=min(f[j]+sumt[i]*(sumf[i]-sumf[j])\ \ ) f[i]=min(f[j]+sumt[i](sumf[i]sumf[j])  )

如若考虑这个s对结果的影响,我们就应该在原先的状态中间对后续的花费都提前累加。

我们知道,对于每一个启动的当前机器i,则第j~n机器的时间都要延迟s。可以在转移f[i]时加上值s*(sumf[n]-sumf[j)。如果分成两段考虑:s*(sum[i]-sum[j]),这是累加当前的时间;s*(sum[n]-sum[i]),这是对后面缺少的值进行提前处理,这样可以时后面的状态转移该状态时因启动时间s拖延的时间也计算在内;同样,i以前拖延的时间也被转以来的状态做了提前计算。因此,在中间的状态上,该状态并非该状态所在表含义的正确值;但是我们所需的结果:f[n]确保证了其正确性。

故状态转移方程为:
f [ i ] = m i n ( f [ j ] + s u m t [ i ] ∗ ( s u m f [ i ] − s u m f [ j ] ) + s ∗ ( s u m f [ n ] − s u m f [ j ] )    ) f[i]=min(f[j]+sumt[i]*(sumf[i]-sumf[j])+s*(sumf[n]-sumf[j])\ \ ) f[i]=min(f[j]+sumt[i](sumf[i]sumf[j])+s(sumf[n]sumf[j])  )

这是利用了费用提前的思想。我们可以用此思想来进行优化。

那么O(n^2)的代码如下:

#include<bits/stdc++.h>
using namespace std;
const int N=400000;
int n,s,t,F;
int sumt[N],sumf[N],f[N];
int main(void)
{
    scanf("%d%d",&n,&s);
    for (int i=1;i<=n;++i)
    {
        scanf("%d%d",&t,&F);
        sumt[i]=sumt[i-1]+t;
        sumf[i]=sumf[i-1]+F;
    }
    memset(f,100,sizeof(f));
    f[0]=0;
    for (int i=1;i<=n;++i)
        for (int j=0;j<i;++j)
            f[i]=min(f[i],f[j]+sumt[i]*(sumf[i]-sumf[j])+s*(sumf[n]-sumf[j]));
    cout<<f[n]<<endl;
} 

可以在洛谷上提交。

O ( n ) O(n) O(n)解法

根据上述方程: f [ i ] = m i n ( f [ j ] + s u m t [ i ] ∗ ( s u m f [ i ] − s u m f [ j ] ) + s ∗ ( s u m f [ n ] − s u m f [ j ] )    ) f[i]=min(f[j]+sumt[i]*(sumf[i]-sumf[j])+s*(sumf[n]-sumf[j])\ \ ) f[i]=min(f[j]+sumt[i](sumf[i]sumf[j])+s(sumf[n]sumf[j])  )
去掉min函数,移项后:
f [ j ] − s ∗ s u m f [ j ] = s u m t [ i ] ∗ s u m f [ j ] + f [ i ] − s u m t [ i ] ∗ s u m f [ i ] − s ∗ s u m f [ n ] + f [ i ] f[j]-s*sumf[j]=sumt[i]*sumf[j]+f[i]-sumt[i]*sumf[i]-s*sumf[n]+f[i] f[j]ssumf[j]=sumt[i]sumf[j]+f[i]sumt[i]sumf[i]ssumf[n]+f[i]
根据斜率优化的套路,化成y=kx+b的一次函数形式:
(不懂套路的可以戳这里,对斜率优化的模板有非常详尽的分析)

然后就是斜率优化模板:因在原先的blog做过详尽分析,这里不再细说。

每一个节点进出队列一次,复杂度O(n)。

#include<bits/stdc++.h>
using namespace std;
const int N=400000;
int n,s,t,F,h;
int sumt[N],sumf[N],f[N],q[N];
#define x(i) (sumf[i])
#define y(i) (f[i]-s*sumf[i])
inline double k(int a,int b)
{
    double dtx=1.0*(x(a)-x(b));
    double dty=1.0*(y(a)-y(b));
    return abs(1.0*dty/dtx);
}
int main(void)
{
    scanf("%d%d",&n,&s);
    for (int i=1;i<=n;++i)
    {
        scanf("%d%d",&t,&F);
        sumt[i]=sumt[i-1]+t;
        sumf[i]=sumf[i-1]+F;
    }
    memset(f,100,sizeof(f));
    f[0]=0;
    h=t=1;
    for (int i=1;i<=n;++i)
    {
        while (h<t && 1.0*k(q[h],q[h+1])<1.0*sumt[i]) h++;
        f[i]=f[q[h]]+sumt[i]*(sumf[i]-sumf[q[h]])+s*(sumf[n]-sumf[q[h]]);
        while (h<t && 1.0*k(i,q[t-1])<1.0*k(q[t],q[t-1])) t--;
        q[++t]=i;
    }
    cout<<f[n]<<endl;
    return 0;
} 
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值