[BZOJ4868][Shoi2017]期末考试(暴力)

中考将至,写此题,中考 rp++ 。

Address

https://www.lydsy.com/JudgeOnline/problem.php?id=4868

Solution

由于期末考试中考的到来,写了这一题。
先把问题抽象化:
n n 个数,分别为 t1,t2,...,tn
还有 m m 个数,分别为 b1,b2,...,bm
操作一:对于一对 ij i ≠ j ,将 bi b i 加一, bj b j 减一的代价为 A A
操作二:对于一个 i ,将一个 bi b i 减一的代价为 B B
上面两种操作可以执行多次。
操作执行完之后,对于任何一个 i ,如果 max{b}>ti max { b } > t i ,则产生的代价为 max{b}ti max { b } − t i
求最小代价和。
把问题拆开,先对于每个 1kmax{b} 1 ≤ k ≤ max { b } ,求出使 max{b} max { b } 下降到 k k 的最小代价和。
显然,如果 AB ,那么操作一是没有意义的。最小代价和为

i=1mmax(bik,0) ∑ i = 1 m max ( b i − k , 0 )

b b 排序后可以求出。
否则可能既有操作一又有操作二(当然操作一要多用)。设 bi<bj ,那么一定有 bi<k<bj b i < k < b j
也就是说,操作一的 i i 一定满足 bi<k j j 一定满足 bj>k
满足 bi<k b i < k b b 值最多可以加一 x=1mmax(kbx,0) 次。
满足 bj>k b j > k b b 值最多可以减一 x=1mmax(bxk,0) 次。
所以,这样操作一的执行次数为:
min(x=1mmax(kbx,0),x=1mmax(bxk,0)) min ( ∑ x = 1 m max ( k − b x , 0 ) , ∑ x = 1 m max ( b x − k , 0 ) )

操作二的执行次数:
|x=1mmax(kbx,0)x=1mmax(bxk,0)| | ∑ x = 1 m max ( k − b x , 0 ) − ∑ x = 1 m max ( b x − k , 0 ) |

求出将 max{b} max { b } 降到 k k 的最小代价和后,再加上 x=1nmax(ktx,0) ,就可以更新答案。
同样将 t t 排序。
特判: C=1016 时,只有 max{b}<min{t} max { b } < min { t } 才能保证最优。为了不爆 long long ,只需要将 k k 1 枚举到 min{t} min { t } 而不是 105 10 5 即可。

Code

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define For(i, a, b) for (i = a; i <= b; i++)
#define Rof(i, a, b) for (i = a; i >= b; i--)
using namespace std;
inline int read() {
    int res = 0; bool bo = 0; char c;
    while (((c = getchar()) < '0' || c > '9') && c != '-');
    if (c == '-') bo = 1; else res = c - 48;
    while ((c = getchar()) >= '0' && c <= '9')
        res = (res << 3) + (res << 1) + (c - 48);
    return bo ? ~res + 1 : res;
}
typedef long long ll; const int N = 1e5 + 5;
int A, B, n, m, t[N], b[N], cntt[N], cntl[N], cntr[N], w1[N], w2[N], MaxN = 1e5;
ll C, sumt[N], suml[N], sumr[N], f[N], Ans = ((1ll << 62) - 1 << 1) + 1;
int main() {
    int i, sp = 1; A = read(); B = read(); cin >> C; n = read(); m = read();
    For (i, 1, n) w1[t[i] = read()]++; For (i, 1, m) w2[b[i] = read()]++;
    For (i, 1, MaxN) cntt[i] = cntt[i - 1] + w1[i], cntl[i] = cntl[i - 1] + w2[i],
        sumt[i] = sumt[i - 1] + 1ll * w1[i] * i,
        suml[i] = suml[i - 1] + 1ll * w2[i] * i; Rof (i, MaxN, 1)
        cntr[i] = cntr[i + 1] + w2[i], sumr[i] = sumr[i + 1] + 1ll * w2[i] * i;
    while (!w2[MaxN]) MaxN--; For (i, 1, MaxN) {
        ll le = 1ll * i * cntl[i] - suml[i], ri = sumr[i] - 1ll * i * cntr[i];
        if (A >= B) f[i] = 1ll * ri * B; else if (le >= ri) f[i] = 1ll * ri * A;
        else f[i] = 1ll * le * A + 1ll * (ri - le) * B;
    }
    while (!w1[sp]) sp++; For (i, 1, (C == 1e16 ? min(sp, MaxN) : MaxN))
        Ans = min(Ans, f[i] + (1ll * i * cntt[i] - sumt[i]) * C);
    cout << Ans << endl; return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值