把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

CF1355E Restorer Distance

题目链接

题目解析

好萎靡呀,又是除了我全场都会系列

为啥要思维定势死磕dp咧(主要是想到了一个类似的题用dp做的 但其实完全不一样啊

讲个笑话:我看出来了长得像二次函数,但是没想到三(啊 我写的san 好像跟删除线叠了)分


首先,如果固定一个所有砖块最后的高度h,我们可以在O(n)复杂度内算出需要的代价。

具体来说,如果M>=R+A,那操作三就不用。否则,我们扫一遍可以知道每块砖变成目标值需要多少次操作一/操作二,把操作一和操作二尽量配对成操作三,剩下的单独操作就可以了。(当然,在写法上,可以直接把M=min(M,R+A),然后直接算,不判断(押个韵

然后,我们就得到了一个rangeh×n的零分优秀算法。


稍微优秀一点,可以将初始高度排序,然后预处理一下前缀和,每次就可以O(1)得出一个h的代价。

这个时候我们盲猜:如果M比较大(具体指M>=R+A),也就是不用操作三,那么操作一和操作二的个数没有影响(这个意思是说,不需要尽量让操作一和操作二的数量平均然后去凑更多的操作三,如果操作三更优的话,说不定可以改变一下h,凑更多的操作三出来,结果更优),所以最终的这个h一定是初始的n个之一;但如果M比较小,就不一定了。但是我们考虑到要尽量凑操作三出来,所以最后的h会落在平均数的附近,枚一枚就好喏。

然后,结合前面的预处理,我们就得到了一个看起来不太靠谱,但实际上可以满分的O(n)优秀算法。


最后,说一下我是怎么觉得它是一个类似于二次函数的东西的呢?

感性理解

之前相当于是我们把操作三变成了操作一和操作二,你贪心地想,如果操作一代价大于操作二代价,那h要选小一点,否则选大一点,但是选太小或者太大也不太行的样子。因为操作数变多了,加起来可能反而不优。举个例子,假如是操作一代价大于操作二代价,而我如果把h选很小,我每把h减小1,右边的数需要更多的操作二,左边的数需要更小的操作一,但是右边的数比左边的数要多,加起来的总代价可能还不如我右边少几次操作二,左边多几次操作一来得好。(更何况还有操作一和操作二数量更接近凑更多操作三出来使答案更优的情况)而这玩意儿就很像初中的二次函数应用题,提高价格顾客变少的那种问题,总括来说就是:yx的一次函数,总收益/代价是W=xy可能再加减乘除个常数什么的,不过那不重要,重要的是W最终是x的二次函数。

理性理解

设最终的高度为h,需要增加a次,减少b次,我们和h+1比较(求个斜率什么的),就可以知道增减情况和变化率。设h变为h+1时的操作一的变化量为x,则新的a=a+x,而x为所有砖块中高度小于等于h的砖块的个数,那么操作二的变化量为nx,则b=b(nx)=b+xn

  1. a<b时:W=a×M+(ba)×RW=(a+x)×M+(ban)×Rdelta=xM+(xn)R=xMnR(分母除以(h+1h)就是斜率,所以这玩意儿就是斜率,或者说导数)
  2. a>b时:W=b×M+(ab)×A,W=(b+xn)M+(ab+n)Adelta=(xn)M+nA=xMnM+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\n",ans);
	return 0;
}
/*
3 100 100 1 
1 3 8
*/
posted @   Starlight_Glimmer  阅读(139)  评论(0编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
浏览器标题切换
浏览器标题切换end
点击右上角即可分享
微信分享提示