poj 1180 Batch Scheduling

Batch Scheduling
Time Limit: 1000MS Memory Limit: 10000K
Total Submissions: 3228 Accepted: 1487

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 + (T x + T x+1 + ... + T x+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. 

Input

Your program reads from standard input. The first line contains the number of jobs N, 1 <= N <= 10000. The second line contains the batch setup time S which is an integer, 0 <= S <= 50. The following N lines contain information about the jobs 1, 2,..., N in that order as follows. First on each of these lines is an integer Ti, 1 <= Ti <= 100, the processing time of the job. Following that, there is an integer Fi, 1 <= Fi <= 100, the cost factor of the job.

Output

Your program writes to standard output. The output contains one line, which contains one integer: the minimum possible total cost.

Sample Input

5
1
1 3
3 2
4 3
2 3
1 4

Sample Output

153

这题光顾着斜率优化,一开始写了一个O(n^2)的程序,TLE了,发现数据量确实有点大,其实这道题有O(n)的复杂度,真是没想到啊。

首先说说,O(n^2)的二维斜率优化,dp[j][i]表示前i个分成j组的最小代价,设st[i]前i个的总时间,sf[i]前i个的总f值,很显然dp[j][i]=min{dp[j-1][k]+(sum[i]-sum[k])*(j*s+st[i])} j-1<=k<i,之后就是常规的二维dp斜率优化了。参考:点击打开链接

还不会斜率dp的快去看看大牛们的博客和论文吧,一定要理解完全凸函数和决策的单调性!

代码:

#include<cstdio>
#include<iostream>
#define Maxn 10010
#define ll long long
using namespace std;

int st[Maxn],sf[Maxn],q[Maxn];
ll dp[2][Maxn];
ll dx(int a,int b,int c){
    return dp[c][b]-dp[c][a];
}
int dy(int a,int b){
    return sf[b]-sf[a];
}
int main()
{
    int n,start;
    while(~scanf("%d%d",&n,&start)){
        for(int i=1;i<=n;i++){
            scanf("%d%d",st+i,sf+i);
            st[i]+=st[i-1];
            sf[i]+=sf[i-1];
        }
        int a=0,b=1;
        for(int i=1;i<=n;i++) dp[a][i]=(st[i]+start)*sf[i];
        ll res=dp[a][n];
        for(int j=2;j<=n;j++){
            int s=0,e=0;
            q[0]=j-1;
            a^=1,b^=1;
            for(int i=j;i<=n;i++){
                while(s<e&&dx(q[s],q[s+1],b)<=dy(q[s],q[s+1])*(ll)(st[i]+j*start)) s++;
                dp[a][i]=dp[b][q[s]]+(st[i]+j*start)*(ll)(sf[i]-sf[q[s]]);
                if(i==n) res=min(res,dp[a][n]);
                while(s<e&&dx(q[e-1],q[e],b)*dy(q[e],i)>=dx(q[e],i,b)*dy(q[e-1],q[e])) e--;
                q[++e]=i;
            }
        }
        printf("%lld\n",res);
    }
	return 0;
}

这里空间开销太大,因此用了滚动数组优化,但这题这样做,很显然超时。


那接下来就来看看神奇的O(n)的复杂度吧,实际上也不神奇,上面的方法是正向推理的,那么不妨试试逆向推理。

设dp[i]表示i到n的最小代价,st[i]表示i->n的总时间,sf[i]表示i->n的总f值,很奇怪吧,为什么这下不同考虑[i,n]分成了几组呢?别急,往下看。

那么dp[i]={dp[j]+f[i]*(s+st[i]-st[j])} i<j<=n+1

这个递推式是什么意思呢?把上面的f[i]拆成两部分,一部分为f[j],另一部分[i...j-1]的总f值,根据乘法分配律:

F[i...j-1]*(s+st[i]-st[j])这部分按照定义就是新的划分出来的一组的cost了,而另一部分f[j]*(s+st[i]-st[j])表示什么呢?很显然,在没划分这句前,dp[j]的总时间算的是从j开始的,因为j之前还没有东西,现在j前加入了i...j-1,那么此时的dp[j]就要变了,总时间应该从i算起,找一下规律发现:从j...n这段每个元素总时间增加了(s+st[i]-st[j]),因此f[j]*(s+st[i]-st[j])+dp[j]不就表示在dp[i]下的dp[j]的值了吗?

很神奇吧,这个dp数组的值并不是不变的,会随着dp推进的方向改变,但幸好还是有关系式联系着,因此仍然可以dp,也就是这个原因,不再需要保留分的组数,那么复杂度降为O(n).

接下来就是一维dp的斜率优化了,更加简单了吧。参考:点击打开链接


代码:

#include<cstdio>
#include<iostream>
#define Maxn 10010
#define ll long long
using namespace std;

int st[Maxn],sf[Maxn],q[Maxn];
ll dp[Maxn];
ll dx(int a,int b){
    return dp[b]-dp[a];
}
int dy(int a,int b){
    return st[b]-st[a];
}
int main()
{
    int n,start;
    while(~scanf("%d%d",&n,&start)){
        for(int i=1;i<=n;i++){
            scanf("%d%d",st+i,sf+i);
        }
        st[n+1]=sf[n+1]=0;
        for(int i=n;i>=1;i--){
            st[i]+=st[i+1];
            sf[i]+=sf[i+1];
        }
        dp[n+1]=0;
        int s=0,e=0;
        q[0]=n+1;
        for(int i=n;i>=1;i--){
            while(s<e&&dx(q[s],q[s+1])<=(ll)sf[i]*dy(q[s],q[s+1])) s++;
            dp[i]=dp[q[s]]+(start+st[i]-st[q[s]])*(ll)sf[i];
            while(s<e&&dx(q[e-1],q[e])*dy(q[e],i)>=dx(q[e],i)*dy(q[e-1],q[e])) e--;
            q[++e]=i;
        }
        printf("%lld\n",dp[1]);
    }
	return 0;
}

真的是一道好题,从什么都不优化O(n^3)的复杂度,通过斜率优化变为O(n^2)的复杂度,继续改变一下思考的方向,通过斜率优化变为O(n)的复杂度。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值