斜率优化

斜率优化是一种优化 dp 的方法,不过在哪之前,我们需要引入一道例题。

点击查看代码 给你一个长度为 n 序列 A,你需要把他分成若干段。定义第 x 段的贡献为: a×(i=lxrxai))2+b×i=lxrxai+c 你需要最大化贡献。 a,b,c 为给定常数。 n106

首先不妨先思考 O(n2) 复杂度怎么做,我们不妨定义 fi 表示以 i 结尾的最大贡献。显然存在 fi=maxj=1i{fi,fj1+a×sum2+b×sum+c} 。我们定义 sum 表示 k=jiak。显然,我们需要优化。

这时候引入斜率优化,不妨将转移点设为 k,那么就会有这样一个式子。fi=fk+sum×a+sum2×b+c。我们定义 si=j=1iaj。原来的式子就会被我们变为: fi=fk+(sisk1)2×a+(sisk1)×b+c

这个式子是可以看做一个一次函数的,我们不妨设 sk1x,这样原来的式子就可以看为:

fi=fk+(six)2×a+(six)×b+c

发现没有?我们把这个玩应打开。

fi=fk+asi22asix+x2a+sibxb+c

我们其实不需要在乎 asi2+sib+c 这个东西,因为对于两个转移点 j,k 来说,这一个部分都是相同的,真正决定大小的显然并不是这一个地方。

也就是说,假设存在两个点 j,k,如果 jk 更优,就一定说明是 fj+2asisj+asj2bsj>fk+2asisk+ask2bsk
这个地方更优秀。
我们继续沿用刚才的思路,设 sj,skx1,x2。原来的式子就相当于
fj+2asjx1+ax12bx>fk+2asix2+ax22bx2

这时候就是斜率优化要做的事情了。我们可以把式子改成:

2asix12asix2>fkfj+ax22ax12bx2+bx1
但是还没完,a<0!!!
所以我们要变号。
2asix12asix2<fkfj+ax22ax12bx2+bx1
也就是说 jk 更优只需要满足这个式子就好了。由于我们 a<0j,k 两点的式子又有单调性。所以本质上我们的最优答案就可以看成一个图。

不难发现,这是一个凸包,又具有单调性,我们可以使用单调队列维护。
具体的,我们每一次都是用队头更新答案(看图你会发现这道题这个东西单调递减)。在更新答案时,我们维护队头元素的点即可。使用我们前面推的式子即可轻松维护。同时,为了保证最优性,所以我们还需要在队尾也这样写。

Code
#include<bits/stdc++.h>
//fj-fk+a(sumj^2-sumk^2)-b(sumj-sumk)/2a*(sumj-sumk) <sumi
using namespace std;
#define int long long
const int Maxn=2e6;
int n,aa,b,c,Sum[Maxn],per[Maxn],dp[Maxn],l,r,q[Maxn];
double slove(int j,int k){
return double( (dp[j]-dp[k]+aa*(Sum[j]*Sum[j]-Sum[k]*Sum[k])+b*(Sum[k]-Sum[j]))/double(2*aa*(Sum[j]-Sum[k])) );
}
signed main(){
cin>>n;
cin>>aa>>b>>c;
for(int i=1;i<=n;i++){
cin>>per[i];
Sum[i]=Sum[i-1]+per[i];
}
//memset(dp,~0x3f,sizeof(dp));
dp[0]=0;
l=r=1;
for(int i=1;i<=n;i++){
while(l<r&&slove(q[l],q[l+1])<=1.0*Sum[i])l++;
dp[i]=dp[q[l]]+(Sum[i]-Sum[q[l]])*(Sum[i]-Sum[q[l]])*aa+b*(Sum[i]-Sum[q[l]])+c;
while(l<=r&&slove(q[r-1],q[r])>=slove(q[r],i)) r--;
q[++r]=i;
}
cout<<dp[n];
return 0;
}
posted @   zhong114514  阅读(7)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示