考虑暴力枚举最晚公布的时间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
#include
#include
#include
#include
#include
#include
#include
#include
#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;
}