Codeforces Round #643 (Div. 2) E.Restorer Distance(三分)

题目

n(1<=n<=1e5)的数组a[],第i个数为ai(0<=ai<=1e9),每次操作你可以选以下三种中的一种

①选择i,令ai+1,代价为A

②选择i,令ai-1,代价为R

③选择i,j,令其中一个-1,另一个+1,代价为M

0<=A,R,M<=1e4,求最终的数组所有值都相等的最小代价

思路来源

https://www.cnblogs.com/1024-xzx/p/12926595.html

https://www.cnblogs.com/Kaike/p/12918815.html

题解

如果只有A和R,显然可以从一边往中间推,第i个向第i+1个转移时,

增量为i*A,减量为(n-i)*R,如果可以推就推,这样就能推到最小点,是开口向上的函数

类似的,若M合适,则最终会只使用AM或RM,同样是两个开口向上的函数

而最终的答案,是这三个函数取min,容易发现也是存在一个最小值点的,可三分

因为最小值只有一个点,这里记录一下三分的两种写法吧

 

官方题解给了一个口胡的证明,设上升个数p和下降个数q,分p<=q和p>q讨论

再设当前高度h,设当前小于h的个数为x,则大于等于的为x+1,

考虑h到h+1的变化,发现代价变化量为x的一次函数,而x发生变化当且仅当在初始高度分界点

说明当p<=q时为若干段分段线性函数,p>q同理,

题解还说明当二者之间变化时还会出现一个极值点\frac{\sum a_{i}}{n}(没太看懂),和所有初始分界点(极值点)取小即可

 

另外,通过以上分析,可以判断是一个连续函数导数线性变化,最多变号一次的单峰函数,

那么分段函数也有如此特性,结合题目性质发现开口向上,果断三分

代码1

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+10;
int n,add,sub,mix,h[N],l,r;
ll cal(int x){
    ll y=0,z=0,sm,v;//add sub
    for(int i=1;i<=n;++i){
        if(h[i]<x)y+=x-h[i];
        else z+=h[i]-x;
    }
    sm=min(y,z);y-=sm;z-=sm;
    v=y*add+z*sub+sm*mix;
    //printf("x:%d cal:%lld\n",x,v);
    return v;
}
int main()
{
    scanf("%d",&n);
    scanf("%d%d%d",&add,&sub,&mix);
    mix=min(mix,add+sub);
    l=1e9+1;r=-1;
    for(int i=1;i<=n;++i){
        scanf("%d",&h[i]);
        l=min(l,h[i]);r=max(r,h[i]);
    }
    while(r-l>2){//考虑[1,3]时m=mm 但1为最小点
        int m=(l+r)/2,mm=(m+r)/2;//m=mm时有r-l<=2
        if(cal(m)<cal(mm))r=mm-1;
        else l=m+1;
    }
    ll ans=cal(l);
    for(int i=l+1;i<=r;++i){
        ans=min(ans,cal(i));
    }
    printf("%lld\n",ans);
	return 0;
}

代码2

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+10;
int n,add,sub,mix,h[N],l,r;
ll cal(int x){
    ll y=0,z=0,sm,v;//add sub
    for(int i=1;i<=n;++i){
        if(h[i]<x)y+=x-h[i];
        else z+=h[i]-x;
    }
    sm=min(y,z);y-=sm;z-=sm;
    v=y*add+z*sub+sm*mix;
    //printf("x:%d cal:%lld\n",x,v);
    return v;
}
int main()
{
    scanf("%d",&n);
    scanf("%d%d%d",&add,&sub,&mix);
    mix=min(mix,add+sub);
    l=1e9+1;r=-1;
    for(int i=1;i<=n;++i){
        scanf("%d",&h[i]);
        l=min(l,h[i]);r=max(r,h[i]);
    }
    while(l<r){
        int lm=l+(r-l)/3,rm=r-(r-l)/3;//m=mm时有r-l<=2
        if(cal(lm)<cal(rm))r=rm-1;
        else l=lm+1;
    }
    printf("%lld\n",cal(l));
	return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Code92007

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值