题目解析
好萎靡呀,又是除了我全场都会系列
为啥要思维定势死磕dp咧(主要是想到了一个类似的题用dp做的 但其实完全不一样啊
讲个笑话:我看出来了长得像二次函数,但是没想到三(啊 我写的san 好像跟删除线叠了)分
首先,如果固定一个所有砖块最后的高度(h),我们可以在(O(n))复杂度内算出需要的代价。
具体来说,如果(M>=R+A),那操作三就不用。否则,我们扫一遍可以知道每块砖变成目标值需要多少次操作一/操作二,把操作一和操作二尽量配对成操作三,剩下的单独操作就可以了。(当然,在写法上,可以直接把(M=min(M,R+A)),然后直接算,不判断(押个韵
然后,我们就得到了一个(range_h imes n)的零分优秀算法。
稍微优秀一点,可以将初始高度排序,然后预处理一下前缀和,每次就可以(O(1))得出一个(h)的代价。
这个时候我们盲猜:如果(M)比较大(具体指(M>=R+A)),也就是不用操作三,那么操作一和操作二的个数没有影响(这个意思是说,不需要尽量让操作一和操作二的数量平均然后去凑更多的操作三,如果操作三更优的话,说不定可以改变一下(h),凑更多的操作三出来,结果更优),所以最终的这个(h)一定是初始的(n)个之一;但如果(M)比较小,就不一定了。但是我们考虑到要尽量凑操作三出来,所以最后的(h)会落在平均数的附近,枚一枚就好喏。
然后,结合前面的预处理,我们就得到了一个看起来不太靠谱,但实际上可以满分的(O(n))优秀算法。
最后,说一下我是怎么觉得它是一个类似于二次函数的东西的呢?
感性理解:
之前相当于是我们把操作三变成了操作一和操作二,你贪心地想,如果操作一代价大于操作二代价,那(h)要选小一点,否则选大一点,但是选太小或者太大也不太行的样子。因为操作数变多了,加起来可能反而不优。举个例子,假如是操作一代价大于操作二代价,而我如果把(h)选很小,我每把(h)减小(1),右边的数需要更多的操作二,左边的数需要更小的操作一,但是右边的数比左边的数要多,加起来的总代价可能还不如我右边少几次操作二,左边多几次操作一来得好。(更何况还有操作一和操作二数量更接近凑更多操作三出来使答案更优的情况)而这玩意儿就很像初中的二次函数应用题,提高价格顾客变少的那种问题,总括来说就是:(y)是(x)的一次函数,总收益/代价是(W=xy)可能再加减乘除个常数什么的,不过那不重要,重要的是(W)最终是(x)的二次函数。
理性理解:
设最终的高度为(h),需要增加(a)次,减少(b)次,我们和(h+1)比较(求个斜率什么的),就可以知道增减情况和变化率。设(h)变为(h+1)时的操作一的变化量为(x),则新的(a'=a+x),而(x)为所有砖块中高度小于等于(h)的砖块的个数,那么操作二的变化量为(n-x),则(b'=b-(n-x)=b+x-n)
- (a<b)时:(W=a imes M+(b-a) imes R),(W'=(a+x) imes M+(b-a-n) imes R,delta=xM+(x-n)R=xM-nR)(分母除以((h+1-h))就是斜率,所以这玩意儿就是斜率,或者说导数)
- (a>b)时:(W=b imes M+(a-b) imes A),(W'=(b+x-n)M+(a-b+n)A,delta=(x-n)M+nA=xM-nM+nA)
那么关于(h)的函数(W)的斜率是一个一次函数(说准确点是分段函数),(x)是随(h)的增大而增大的分段函数。而且斜率逐渐变大,(先负后正,刚开始(x)很小的时候,(xM<nR))的一次函数,所以应该是类似于开口朝上的二次函数,不过因为(x)是整数,并且它关于(h)不是连续变化的,所以斜率也不是连续变化的,而是在一段区间内斜率一样,那么(W)就是一个下凸包(是一截一截的,而不是二次函数那样比较光滑的曲线)。
但是由于我对三分这个算法太不熟悉了,所以并没有想到它嘤嘤嘤。
事实上,我们之前说了斜率是一次函数,那么可以还对斜率进行二分。实际上,三分和二分斜率是等价的,因为单峰函数的斜率是单调的,并且在最值斜率为(0)。
然后,你就得到了一个看以来要靠谱一些的满分做法。
(我是不是跟分治有仇啊(qwq) 儒略历那道题用二分要简单多了 但我就是没有想到二分 然后直接计算模拟硬刚 最后惨挂(60pts qwq)
►Code View
#include<cstdio>
#include<algorithm>
#include<queue>
#include<cstring>
using namespace std;
#define N 100005
#define M 200005
#define MOD 998244353
#define INF 0x3f3f3f3f3f3f3f3f
#define LL long long
LL rd()
{
LL x=0,f=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-') f=-1; c=getchar();}
while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48); c=getchar();}
return f*x;
}
int n;
LL a,r,m,h[N],ans=INF;
LL opt1,opt2;
void calc(int i,int j)
{
if(h[i]==j) return ;
if(h[i]<j) opt1+=j-h[i];
else opt2+=h[i]-j;
}
LL work()
{
if(m<a+r)
{
LL k=min(opt1,opt2),res=0;
res=k*m,opt1-=k,opt2-=k;
if(opt1) res+=opt1*a;
if(opt2) res+=opt2*r;
return res;
}
else return opt1*a+opt2*r;
}
LL f(LL het)
{
opt1=opt2=0;
for(int i=1;i<=n;i++)
calc(i,het);
return work();
}
int main()
{
//freopen("bricks.in","r",stdin);
//freopen("bricks.out","w",stdout);
n=rd(),a=rd(),r=rd(),m=rd();
for(int i=1;i<=n;i++)
h[i]=rd();
sort(h+1,h+n+1);
int l=h[1],r=h[n];
while(l+10<r)
{
int lmid=l+(r-l)/3,rmid=r-(r-l)/3;
LL lans=f(lmid),rans=f(rmid);
if(lans>rans) l=lmid;
else r=rmid;
}
LL ans=INF;
for(int i=l;i<=r;i++)
ans=min(ans,f(i));
printf("%lld
",ans);
return 0;
}
/*
3 100 100 1
1 3 8
*/