【BZOJ 4868】【2017六省联考】期末考试

考虑暴力枚举最晚公布的时间x,关注到2操作是没有负面影响的1操作,所以如果A大于B,那么只需用2操作就可以了,否则先用1操作,不能用1操作后再用2操作。这样就能把b数组全部变成小于等于x,在加上额外的不愉快度就可以了。这个算法的时间复杂度是 O(N2) ,可以拿60分。
如果你去打表就能发现不愉快度关于时间是一个下凸函数,可以用三分做。具体的证明是这样的:
1、修改代价关于时间是单调递减的,也就是说越晚出成绩修改代价越小;
2、额外代价关于时间是单调递增的,也就是说越晚出成绩额外代价越大;
3、最重要的一句,这两个函数的导数都是递增的。
为什么呢?感性的想一下,拿额外代价举例子,比如五个人的时间分别是1、2、2、3、3,时间从1改到2,需要额外代价的1个人(第一个人),但是从2改到3,需要额外代价的就变成了3个人,从3改到4所有的人都需要额外代价,也就是说刚开始额外代价增加慢,到后来额外代价增加快。
4、这样把这两个函数加起来,导数也是递增的,所以它是一个下凸函数(当然也有可能退化成单调函数)。

我写的就是三分。不过听说还能二分(二分导数?)正解中还有一种做法是用DDL做,就可以做到线性orz
这道题最恶心的是中间有一组C=1e16,被坑死了,直接特判掉即可。注意那个等号,因为两个都是inf,答案要更小。

#include<cmath>
#include<cstdio>
#include<vector>
#include<queue>
#include<cstring>
#include<iomanip>
#include<stdlib.h>
#include<iostream>
#include<algorithm>
#define ll long long
#define mod 1000000007
#define N 100005
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--)
using namespace std;
ll A,B,C,l,r,res,res1,r1,r2,inf;
int n,m,i,j,mid1,mid2;
int t[N],b[N];
ll calc(int x)
{
    int i; ll sum = 0,need = 0;
    fo(i,1,m) if (b[i] <= x) sum += x - b[i];
    fo(i,1,m) if (b[i] > x) need += b[i] - x;
    if (A < B)
        {
            if (need <= sum) return (ll)A*need;
            return (ll)(A*sum+B*(need-sum));
        }
    return (ll)B*need;
}
int main()
{
    inf = 1; fo(i,1,62) inf *= 2;
    scanf("%lld%lld%lld",&A,&B,&C);
    scanf("%d%d",&n,&m);
    fo(i,1,n) scanf("%d",&t[i]);
    l = inf; r = -inf;
    fo(i,1,n) r = max(r,(ll)t[i]) , l = min(l,(ll)t[i]);
    fo(i,1,m) scanf("%d",&b[i]);

    while (r - l > 5)
        {
            mid1 = l + (r - l) / 3;
            mid2 = mid1 + (r - l) / 3;
            r1 = calc(mid1); r2 = calc(mid2);
            fo(j,1,n) if (t[j] < mid1) 
                if (C == 1e16) {r1 = inf; break;} else r1 += C * (mid1 - t[j]);
            fo(j,1,n) if (t[j] < mid2) 
                if (C == 1e16) {r2 = inf; break;} else r2 += C * (mid2 - t[j]);
            if (r1 <= r2) r = mid2; else l = mid1;
        }
    res = -1;
    fo(i,l,r)
        {
            res1 = calc(i);
            fo(j,1,n) if (t[j] < i) 
                if (C == 1e16) {res1 = inf; break;} else res1 += C * (i - t[j]);
            if (res == -1) res = res1;
            res = min(res,res1);
        }
    printf("%lld\n",res);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值