Time Limit: 1000MS | Memory Limit: 10000K | |
Total Submissions: 3228 | Accepted: 1487 |
Description
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
Output
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)的复杂度。