生产一台汽车需要从1号工人开始,当1号完成他的工作后,2号就会开始工作,然后是3号,最后当N号工人完成他的工作后,整个汽车生产完毕。工人们一共需要生产M台汽车,而且必须按照从1到M的顺序去生产。
对于工人i,他完成自己的工作需要Ti的时间,而对于汽车j,组装复杂度为Fj。那么工人i花在汽车j上的时间为Ti*Fj。
当某个工人完成他的工作后,他会同时把汽车交给下一个工人,没有任何时间上的延迟,因此,要保证下一个要接受汽车的工人必须是空闲的。为了满足这个要求,ABC需要为每一台汽车选择一个好时机开始制造。
ABC想知道生产完所有汽车最少需要多少时间。
Solution
经过分析可发现:
设g[i]为i车最早生产时间。g[1]=0
g[i]=g[i−1]+Maxni=1(sum[j]∗f[i−1]−sum[j−1]∗f[i])
令Gj=(sum[j]∗f[i−1]−sum[j−1]∗f[i])
看着这相似的结构,考虑斜率优化。
假如有k , k>j , Gk>Gj也就是k决策点优于j
将G展开得到
fifi−1<sumk−sumjsumk−1−sumj−1
我们可以将每个决策视为二维平面上的点(sumk,sumk−1)
假设有三个点a,b,c
令T(a,b)=sumb−sumasumb−1−suma−1
若b是决策点当且仅当T(a,b)>const,T(b,c)<const=>T(a,b)>T(b,c)
这意味这我们要维护斜率单调减,维护出一个下凸壳,在凸壳上二分出决策点
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std ;
#define N 100010
typedef long long ll ;
ll n , m , t[N] , sum[N] , f[N] , g[N] , q[N] , i , j , k ;
ll cross( ll x , ll y , ll a , ll b ) {
return x * b - y * a ;
}
int main () {
scanf("%lld%lld",&n,&m ) ;
for( i=1 ; i<=n ; i++ ) {
scanf("%lld",&t[i] ) ;
sum[i] = sum[i-1] + t[i] ;
}
for( i=1 ; i<=m ; i++ ) scanf("%lld",&f[i] ) ;
int l = 1 , r = 0 ;
for( i=1 ; i<=n ; i++ ) {
while( l<=r-1 && cross( sum[i-1]-sum[ q[r-1]-1 ],sum[i]-sum[ q[r-1] ] , sum[ q[r]-1 ]-sum[ q[r-1]-1 ],sum[ q[r] ]-sum[ q[r-1] ] ) < 0 ) r-- ;
q[++r] = i ;
}
g[1] = 0 ;
for( i=2 ; i<=m ; i++ ) {
int L = l , R = r-1 ;
double va = ( f[i] ) / double( f[i-1] ) ;
while( L<=R ) {
int M = ( L + R ) / 2 ;
if( ( sum[ q[M+1] ]-sum[ q[M] ] ) / double( sum[ q[M+1]-1 ]-sum[ q[M]-1 ] ) > va ) L = M + 1 ; else R = M - 1 ;
}
g[i] = g[i-1] + sum[ q[L] ] * f[i-1] - sum[ q[L]-1 ] * f[i] ;
}
printf("%lld\n", g[m] + sum[n] * f[m] ) ;
}