斜率优化dp小结
这个真的好容易啊
个人总结出斜率优化一般步骤:
- 写出
的 1d/1d 转移方程:即状态与转移都是一维的方程 - 将dp式化为
的一次函数形式,尽可能保证斜率 ,自变量 的单调性,将本轮答案 置于由常数和只与 相关变量组成的截距 中。将转移用的 放置于因变量 中。 - 通过斜率与自变量的单调性维护凸包。
按照步骤,我们假设
检验正确性后进行如下操作:
- 假设当前的
就是转移的最优解 - 省略极值符号,将转移方程按照上文步骤拆解
- 根据所得方程进行凸包维护
不妨使用前缀和维护士兵的初始战力和
转化为
注意到截距
转移
此时斜率固定,图上的黄点所对应的蓝点即截距
因而我们注意到最下面的点不可能成为最优解,然而斜率
保留的四个点代表了随着斜率的减小而取得的最优解。形成了一个上凸包
考虑如何取舍当前
观察得:斜率
因此我们用单调队列维护斜率,由于当前斜率
通过刚才的斜率比对并修改队头,最优的下标即
黄点,也就是当前已有的用来转移的点不是凭空出现的,当前转移的点
翻译成线性规划:
此时
#include<bits/stdc++.h>
#define int long long
#define MAXN 1000005
using namespace std;
int n;
struct function{
int a,b,c;
}f;
int sum[MAXN];
int dp[MAXN];
int q[MAXN],h,t;
int Y(int p){//根据上文分析出的X,Y,K等写出对应函数
return dp[p]+f.a*sum[p]*sum[p]-f.b*sum[p];
}
int K(int p){
return 2*f.a*sum[p];
}
double slope(int x1,int x2){
return (Y(x1)-Y(x2))/(sum[x1]-sum[x2])*1.0;
}
signed main(){
scanf("%lld",&n);
scanf("%lld%lld%lld",&f.a,&f.b,&f.c);
for(int i=1;i<=n;i++){
scanf("%lld",&sum[i]);
sum[i]+=sum[i-1];
}
for(int i=1;i<=n;i++){
while(t>h&&slope(q[h],q[h+1])>K(i))++h;//找到第一个斜率小于i对应斜率的线段
dp[i]=dp[q[h]]+f.a*(sum[i]*sum[i]-2*sum[i]*sum[q[h]]+sum[q[h]]*sum[q[h]])+f.b*(sum[i]-sum[q[h]])+f.c;//按照方程转移
while(t>h&&slope(q[t],i)>=slope(q[t],q[t-1]))--t;//将不够优秀的点弹出,点i入队。
q[++t]=i;
}
printf("%lld",dp[n]);
return 0;
}
四张图的K是
令
获得了一个癌症级别的多项展开式。
用前缀和替换并移项。
不妨让
然后开始拆解。
转化为
其中,
同理,第一个大于第
线性规划来看,一个点比之前的黄点更优,当且仅当该点在线段下方。
套板子即可。
#include<bits/stdc++.h>
#define int long long
#define MAXN 50005
using namespace std;
int n,l;
int w[MAXN];
int sum[MAXN];
int dp[MAXN];
int q[MAXN],h,t;
int Y(int x){
return dp[x]+sum[x]*sum[x]+2*l*sum[x];
}
int X(int x){
return sum[x];
}
double slope(int x_1,int x_2){
return 1.0*(Y(x_1)-Y(x_2))/(X(x_1)-X(x_2));
}
signed main(){
scanf("%lld%lld",&n,&l);
++l;
for(int i=1;i<=n;i++){
scanf("%lld",&w[i]);
sum[i]=sum[i-1]+w[i]+1;
}
for(int i=1;i<=n;i++){
//dp[i]=min(dp[i],dp[j]+(sum[i]-sum[j]+(i-j-1)-l)*(sum[i]-sum[j]+(i-j-1)-l)); lsum
//dp[i]=dp[j]+(sum[i]-sum[j]-l)(sum[i]-sum[j]-l)
//dp[i]=dp[j]+(sum[i]-sum[j])2+l2-2l(sum[i]-sum[j])
//dp[i]=dp[j]+sum2[i]+sum2[j]-2sum[i]sum[j]+l2-2lsum[i]+2lsum[j]
// Y = k X + B
//dp[j]+sum2[j]+2lsum[j]=2sum[i]sum[j]+dp[i]-sum2[i]+2lsum[i]-l2
while(t>h&&slope(q[h],q[h+1])<=2.0*sum[i])++h;//找最优解
dp[i]=dp[q[h]]+(sum[i]-sum[q[h]]-l)*(sum[i]-sum[q[h]]-l);//转移
while(t>h&&slope(q[t-1],q[t])>=slope(q[t-1],i))--t;//更新答案
q[++t]=i;
}
printf("%lld",dp[n]);
return 0;
}
(*)性质是一个大前提,事实上这个大前提是可以不成立的。
先推
把方程拆开,借助题解发现方程的两部分可以分开处理。自
如果分组左端点下标组成的集合为
脑子不好想不来所以本人还画了一个图:
每个分组对总影响各取所需即可。但是我们只需要
顺带转化为前缀和形式:
然后按照之前的方法拆解。
尝试进一步分析时发现了问题:
不过这不影响决策点的位置:第一个斜率大于
不过此时要用二分查找。
当前点比原先黄点更优,当且仅当其于末位黄点下方。
#include<bits/stdc++.h>
#define int long long
#define MAXN 300005
using namespace std;
int n,s;
struct mission{
int c,t;
}p[MAXN];
int csum[MAXN],tsum[MAXN];
int dp[MAXN];
int q[MAXN],h,t;
int Y(int x){
return dp[x]-s*csum[x];
}
int X(int x){
return csum[x];
}
double slope(int x_1,int x_2){
return 1.0*(Y(x_1)-Y(x_2))/(X(x_1)-X(x_2));
}
int geth(int x){
int l=h,r=t,res=r;//res始终没有更新说明该点的斜率是有史以来最大的,最优解为队尾。
while(l<=r){
int mid=l+r>>1;
if(slope(q[mid+1],q[mid])>=(1.0*tsum[x]))r=mid-1,res=mid;
else l=mid+1;
}
return q[res];
}
signed main(){
scanf("%lld%lld",&n,&s);
for(int i=1;i<=n;i++){
scanf("%lld%lld",&p[i].t,&p[i].c);
tsum[i]=tsum[i-1]+p[i].t;
csum[i]=csum[i-1]+p[i].c;
}
for(int i=1;i<=n;i++){
int loc=geth(i);
dp[i]=dp[loc]+tsum[i]*(csum[i]-csum[loc])+s*(csum[n]-csum[loc]);
while(t>h&&slope(i,q[t-1])<=slope(q[t],q[t-1]))--t;
q[++t]=i;//正常处理
}
printf("%lld",dp[n]);
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!