GDOI2016模拟8.13生产汽车

题目
如前面提到,ABC的汽车工厂有N个工人,他们在一个传送带上生产汽车,工人从左到右排列,编号依次为1到N,采用流水线模式,每个人负责自己的一部分工作。
生产一台汽车需要从1号工人开始,当1号完成他的工作后,2号就会开始工作,然后是3号,最后当N号工人完成他的工作后,整个汽车生产完毕。工人们一共需要生产M台汽车,而且必须按照从1到M的顺序去生产。
对于工人i,他完成自己的工作需要Ti的时间,而对于汽车j,组装复杂度为Fj。那么工人i花在汽车j上的时间为Ti*Fj。
当某个工人完成他的工作后,他会同时把汽车交给下一个工人,没有任何时间上的延迟,因此,要保证下一个要接受汽车的工人必须是空闲的。为了满足这个要求,ABC需要为每一台汽车选择一个好时机开始制造。
ABC想知道生产完所有汽车最少需要多少时间。

理顺一下题意

我们可以得到答案T是由相邻两次生产的最小空余时间加上最后一个生产的时间。

那么相邻两次生产的最小空余时间怎么求呢?
我们设当前的生产为第j个,则对于每个工人,满足:
T+All[i-1]*Fj≥All[i]*F[j-1](All[i]为前i个工人的效率和,也就是Ti的前缀和)

这里大家可以尝试着继续变形下去,用斜率优化做,时间复杂度为O(n)的

但这里我想讲一个新的方法:凸包+三分+随机化

大家是不是想不到为什么能用凸包?

那就对了,出题人也没想到,数据也不知道,没有针对性的数据,凸包上的点期望是log个,直接暴力就行,但我想讲个靠谱点的:

对于那条不等式,我们可以发现

T=max(All[i]*F[j-1]-All[i-1]*Fj)

是不是很像叉积啊?

由于固定的是All[i]和All[i-1],那么我们将其变成点(All[i],All[i-1]),做个凸包,那么T就是凸包上所有点对(F[j],F[j-1])做叉积的最大值,为什么是凸包上的点就行呢?
对于叉积,我们可以看做两点与原点形成的三角形的面积的两倍,由于点(F[j],F[j-1])在询问时是固定的,那么固定了一条边,要使得三角形面积最大,则高越长,也就是找距直线OA(A为(F[j],F[j-1]))最远的位于直线顺时针方向的点,易证:对于所有的直线,凸包上的点就可以维护所有的答案。

作图我们可以发现,对于任意直线,凸包上的点对答案的贡献是一个双峰函数,通常形状如下图:

这里写图片描述

这样我们可以用随机化(随机一个切割点,使其变成单峰函数)+三分,由于卡的数据比较难出(首先,你得使峰和谷间的点尽量少,两端的点尽量多,而且要避免凸包使得点数变少,才能使随机分割点的期望降低,何况这是个随机化的做法,而且对于一个询问而言是复杂度提高了,但其他询问有可能函数的图像会有所改变,不能同时卡多个点,不然加个记忆化也可以),当然,实在不放心,也可以用模拟退火,数学能力较强的,也可以画图,想一下如何用询问的直线平移后将其切割成上下凸壳,直接将其变成两个单峰函数,由于一个为负,只做一个就行了。

总的来说,各种方法的期望都是n((logn)^2)

****补充一下,刚才打字的时候,凝视了一下贴上来的图,发现,若将分割点得到的答案看成函数,貌似是一个单峰函数,大家可以尝试一下三分分割点,说不定是对的,若验证出来的(当然用人工出的数据来卡才行),可以评论分享一下,有别的不和斜率优化相关的思路也可。
贴代码

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<ctime>
#define random(x)(rand()%x)
using namespace std;
#define N 100001
#define TIMES 28
#define PI 3.1415926535898
int n,m,L,R,len;
int f[N],a[N][2],b[N],d[N];
long long T;
long double c[N];
void init(){
    static int x,y;
    scanf("%d %d",&n,&m);
    y=0;
    for (int i=1;i<=n;i++)
        scanf("%d",&x),a[i][1]=y,a[i][0]=(y+=x);
    for (int i=1;i<=m;i++)
        scanf("%d",&f[i]);
}
long double did(long double x){
    return x<0?x+PI+PI:x;
}
bool jian(int x,int y,int x1,int y1,int x2,int y2){
    x1-=x,y1-=y,x2-=x,y2-=y;
    return (long long)x1*y2-(long long)x2*y1<=0;
}
bool cmp(int x,int y){
    return c[x]<c[y];
}
void pre(){
    static long double midx,midy;
    midx=midy=0;
    for (int i=1;i<=n;i++)
        b[i]=i,midx+=a[i][0],midy+=a[i][1];
    midx/=n,midy/=n;
    for (int i=1;i<=n;i++)
        c[i]=did(atan2((long double)a[i][1]-midy,(long double)a[i][0]-midx));
    sort(b+1,b+n+1,cmp);
    L=1,R=1;
    d[1]=b[1];
    for (int i=2;i<=n;i++){
        while (R-L>1&&jian(a[d[R-1]][0],a[d[R-1]][1],a[d[R]][0],a[d[R]][1],a[b[i]][0],a[b[i]][1]))R--;
        d[++R]=b[i];
    }
    while (R-L>2&&jian(a[d[R-1]][0],a[d[R-1]][1],a[d[R]][0],a[d[R]][1],a[d[L]][0],a[d[L]][1]))R--;
    while (R-L>2&&jian(a[d[R]][0],a[d[R]][1],a[d[L]][0],a[d[L]][1],a[d[L+1]][0],a[d[L+1]][1]))L++;
    len=R-L+1;
}
long long treed(int x){
    static int x2,y2;
    x2=f[x],y2=f[x-1];
}
long long calc(int x,int y){
    return (long long)a[d[y]][0]*f[x-1]-(long long)a[d[y]][1]*f[x];
}
long long treed(int l,int r,int x){
    static int mid1,mid2,len,L,R;
    L=l,R=r;
    while (l<=r){
        len=r-l;
        mid1=l+len/3;
        mid2=r-len/3;
        if (calc(x,mid1)<calc(x,mid2))l=mid1+1;
        else
            r=mid2-1;
    }
    if (l>R)l=R;
    if (l<L)l=L;
    while (l>L&&calc(x,l-1)>calc(x,l))l--;
    while (l<R&&calc(x,l+1)>calc(x,l))l++;
    return calc(x,l);
}
long long get(int x){
    static long long s,mid;
    s=0;
    s=max(s,(long long)f[x-1]*a[d[R]][0]-(long long)f[x]*a[d[R]][1]);
    s=max(s,(long long)f[x-1]*a[d[L]][0]-(long long)f[x]*a[d[L]][1]);
    for (int i=1;i<=TIMES;i++){
        mid=L+random(len);
        s=max(treed(L,mid,x),s),s=max(treed(mid,R,x),s);
        s=max(s,(long long)f[x-1]*a[d[mid]][0]-(long long)f[x]*a[d[mid]][1]);
    }
    return s;
}
void work(){
    T=(long long)f[m]*a[n][0];
    for (int i=2;i<=m;i++)
        T+=get(i);
}
void write(){
    printf("%lld",T);
}
int main(){
    srand((int)time(0));
    init();
    pre();
    work();
    write();
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值