【BZOJ4868】期末考试(六省联考2017)-三分

测试地址:期末考试
做法:本题需要用到三分。
我们令最慢出成绩的时刻为 T T ,考虑找到f(T):所有学科的成绩都在时刻 T T 内出的最小不满意度。
注意到不满意度可以分为两个部分:学生的不满意度和老师的不满意度。学生的不满意度十分好算,主要是老师的不满意度要怎么算。注意到,要使所有学科的成绩都在时刻T前出,实际上就是把超出的时间删去或者移动到其它科的时间上去。当 AB A ≥ B 时,直接把超出的时间删掉即可。当 A<B A < B 时,我们要先尽可能地把超出的时间移动到其他学科上去,然后如果还有超出,就只能把超出的时间删去了。
得到了最优策略,就能算出 f(T) f ( T ) 了。注意到,学生的不满意度随着 T T 的增大而增大,并且增大的速度不断增大(可以理解为类似于导数的东西),老师的不满意度随着T的增大而减小,并且减小的速度不断减小,所以它们的和 f(T) f ( T ) 就是一个下凸函数,对区间 [min(ti),max(ti)] [ min ( t i ) , max ( t i ) ] 应用三分法解决即可(因为函数只有在这个区间内是一定严格递增/递减的)。计算一次 f(T) f ( T ) 的时间是 O(n) O ( n ) 的,所以总的时间复杂度是 O(nlogn) O ( n log ⁡ n )
注意一个非常坑爹的点,有两个点 C=1016 C = 10 16 ,直接计算会超long long,实际上在这种情况下,学生的不满意度必须是 0 0 ,否则不管怎么样都无法弥补巨大的不满意度,所以直接输出f(min(ti))即可。
以下是本人代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll A,B,C,mint=1000000000,maxt=0,sumb=0;
ll t[100010],b[100010];
int n,m;

ll f(ll T)
{
    ll ans=0,need=0,tot=0;
    for(int i=1;i<=n;i++)
        if (T>t[i]) ans+=C*(T-t[i]);
    for(int i=1;i<=m;i++)
    {
        if (b[i]>T) need+=b[i]-T;
        else tot+=T-b[i];
    }
    if (A<B)
    {
        ans+=A*min(need,tot);
        ans+=B*max(need-tot,(ll)0); 
    }
    else ans+=B*need;
    return ans;
}

ll three_divide(ll l,ll r)
{
    while(l<r-1)
    {
        ll mid=(l+r)/2;
        ll mmid=(mid+r)/2;
        if (f(mid)<f(mmid)) r=mmid;
        else l=mid;
    }
    return f(l)<f(r)?f(l):f(r);
}

int main()
{
    scanf("%lld%lld%lld",&A,&B,&C);
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        scanf("%lld",&t[i]);
        mint=min(mint,t[i]),maxt=max(maxt,t[i]);
    }
    for(int i=1;i<=m;i++)
    {
        scanf("%lld",&b[i]);
        sumb+=b[i];
    }

    if (C>100000) printf("%lld",f(mint)); 
    else printf("%lld",three_divide(mint,maxt));

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值