P2365 任务安排 batch 动态规划

batch

★☆   输入文件:batch.in   输出文件:batch.out   简单对比
时间限制:1 s   内存限制:128 MB

题目描

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

例如:S=1T={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写挂了 QAQ

 

正解1   

f[i]   前i个任务的最优结果

f[i]=min{f[j] +sumt[i]*(sumc[i]-sumc[j])+S*(sumc[n]-sumc[j])}

好东西呢 唉 QAQ

 

 

 

详细题解如下:

 

 

暴力方法

由于每一批任务连续,所以预处理出两个前缀和数组:

 

sumt[i] 表示 执行前i个任务所需要的时间 , 即t[1]+t[2]+...+t[n]

sumc[i] 表示 不乘时间时,执行前i个任务所需要的费用 , 即c[1]+c[2]+...+c[n]

 

dp子状态:

 

dp[i][j] 表示 前i个任务分成j批所需要的最小费用。

 

于是可以由dp[k][j-1]推出dp[i][j]:

 

dp[i][j]=min{dp[k][j-1]+(s*j+sumt[i])*(sumc[i]-sumc[k])}

 

时间复杂度O(n^3),

空间复杂度O(n^2)。

 

略微优化

暴力方法的dp子状态空间是二维的,由于N <= 10000 ,所以考虑将二维状态降到一维。

将第一维去掉的想法不太实际,故考虑将第二维去掉。

首先思考所有任务都放到同一批中,观察每个任务需要等待S时间的次数:

 

任务编号 1  2  3  ...  n

等待次数 1  1  1  ...  1

 

当将1~pos1分成一个区间时,每个任务需要等待的次数变为:

 

任务编号 1  2  3  ...  pos1  pos1+1  ...  n

等待次数 1  1  1  ...   1      2     ...  2

 

(如果仍然看不出来,可以再分几次找找规律)

观察等待次数:每次多分出一个区间,区间左端点到n都需要多等待一次S时间。

故可以推出一个新的方程:

 

dp[i]=min{dp[j]+sumt[i]*(sumc[i]-sumc[j]+s*(sumc[n]-sumc[j]))

dp[i]并不只是前i个的时间  还要加上对后面的影响

 

 

 

上面其实就已经AC了  (但是老师的本意是要让我们写斜率优化)

 

 

 

 

 

 

斜率优化

当每次求dp[i]时,分别整理i,j的信息,把原来的方程进行玄学变形(移项):

 

dp[i]=min{dp[j]-(s+sumt[i])*sumc[j]}+sumt[i]*sumc[i[]+s*sumc[n]

 

去掉min函数,把dp[j]和sumc[j]看作变量,整理出dp[j]关于sumc[j]的一次函数(重要!):

 

dp[j]=(s+sumt[i]) * sumc[j] + (dp[i]-sumt[i]*sumc[i]-s*sumc[n])

  y  =    k       *    x    +                 b

 

其中sumc[j]是自变量,dp[j]是因变量,s+sumt[i]是斜率,后面一串为截距。

建立一个平面直角坐标系:

每个决策j的二元组(sumc[j] , dp[j])表示坐标系中的点。

当前状态dp[i]表示直线的截距(且这条直线斜率为s+sumt[i])。

令直线过每个点可以得到解出截距,使截距最小的就是最优决策。

如图:

 

 

讨论三个决策点j1,j2,j3(j1<j2<j3)对应的坐标系中的点:

 

 

第一种情况:上凸。用眼睛观察(hhh)可知,j2此时不可能成为最佳决策点,故放弃。

 

 

 

第二种情况:下凹。此时j2有可能成为最佳决策点。

最后把所有可能成为最佳决策点的j处理出来,即维护一个相邻点斜率单调递增的“凸壳”。

其中最佳决策点左端斜率<s+sum[i],右端斜率>s+sum[i](可以证明这样的点只有一个)。

又因为s+sumt[i]是单调递增的,所以可以用单调队列找第一个右端斜率>s+sum[i]的点。

 

总时间复杂度:循环递推O(n)+单调队列O(n)=O(n)。

空间复杂度:一维状态O(n)。

 

 

#include <stdio.h>
typedef long long ll;
typedef double db;
const int N=1e6+10;
int n,S,s[N],t[N],q[N],head,tail;
ll dp[N],val[N];
db k(int j,int k){return (db)(val[j]-val[k])/(s[j]-s[k]);}
int main()
{
    freopen("batch.in","r",stdin);
    freopen("batch.out","w",stdout);
    scanf("%d%d",&n,&S);
    for (int i=1;i<=n;i++){
        scanf("%d%d",&t[i],&s[i]);
        t[i]+=t[i-1];
        s[i]+=s[i-1];
    }
    q[head=tail=1]=0;
    for (int i=1;i<=n;i++){
        for (;head<tail&&k(q[head],q[head+1])<S+t[i];head++);
        int j=q[head];
        dp[i]=dp[j]+ll(s[n]-s[j])*(S+t[i]-t[j]);
        val[i]=dp[i]+(ll)s[i]*t[i]-(ll)s[n]*t[i];
        for (;head<tail&&k(q[tail-1],q[tail])>k(q[tail],i);tail--);
        q[++tail]=i;
    }
    printf("%lld\n",dp[n]);
    return 0;
}

 N^2暴力AC!

#include<bits/stdc++.h>
#define maxn 5005
using namespace std;
int t[maxn],f[maxn],sumt[maxn],sumf[maxn],dp[maxn];
int main(){
//    freopen("batch.in","r",stdin);freopen("batch.out","w",stdout);
    int n,s;scanf("%d%d",&n,&s);
    for(int i=1;i<=n;i++) scanf("%d%d",&t[i],&f[i]);
    for(int i=1;i<=n;i++) sumt[i]=sumt[i-1]+t[i],sumf[i]=sumf[i-1]+f[i];
    memset(dp,0x3f,sizeof(dp));
//    dp[1]=t[1]*f[1]+s*(sumf[n]-sumf[1]);
    dp[0]=0;
    for(int i=1;i<=n;i++)
        for(int j=0;j<i;j++)
            dp[i]=min(dp[i],dp[j]+sumt[i]*(sumf[i]-sumf[j])+s*(sumf[n]-sumf[j]));
    printf("%d",dp[n]);
    //f[i]=min{f[j] +sumt[i]*(sumc[i]-sumc[j])+S*(sumc[n]-sumc[j])}
    return 0;
}

 

posted @ 2019-08-09 10:59  DreamingBligo_Tido  阅读(225)  评论(0编辑  收藏  举报