斜率优化DP复习笔记
前言
复习笔记2nd。
Warning:鉴于摆渡车是普及组题目,本文的难度定位在普及+至省选-。
参照洛谷的题目难度评分(不过感觉部分有虚高,提高组建议全部掌握,普及组可以选择性阅读。)
引用部分(如这个文本)为总结性内容,建议即使是跳过部分也进行阅读。
0——P3195[HNOI2008]玩具装箱
题目链接
怎么一上来就是紫题啊
题意
给定 \(C_i\) 表示每个物体长度,把 \(i\sim j\) 的物品放入一个容器中,容器的长度为 \(x=j-i+\sum_{k=i}^j C_k.\) 一个长度为 \(x\) 的容器制作费用为 \((x-L)^2\) ,求装下所有物品的最小费用。
思路
设 \(S[n]=\sum( C_i+1),f[i]\) 为装好前 \(i\) 个的最小花费,转移方程为 :
将 \(L\) 提前加一,去掉 \(min\) 化简得:
把平方拆开得到:
下面将描述如何进行斜率优化。
斜率优化的一般方式
注:此处对应讲解的“线性规划”部分,个人认为比较便于理解。
对于上面那个式子,进行移项,使得变成形如 \(y=kx+b\) 的形式。
移项遵循原则:把含有 \(g(i)\times g(j)\) 的表达式看做斜率 \(k\) 乘以未知数 \(x\) ,含有 \(f[i]\) 的项必须在 \(b\) 的表达式中,含有 \(g(j)\) 的项必须在 \(y\) 的表达式中。为了方便分析,如果 \(x\) 的表达式单调递减,等式两边同乘 \(-1\) 变为单增。
那么原式就可以化为:
\[2(S[i])S[j]+(f[i]-S[i]^2+2S[i]L)=(f[j]+(S[j]+L)^2) \]其中,一次函数的各个项分别对应:
\[k_i=2S[i],x_i=S[j],b_i=f[i]-S[i]^2+2S[i]L,y_i=f[j]+(S[j]+L)^2 \]对于这个式子,我们的目的是求出一个 \(j\) 使得 \(f[i]\) 最小,又有 \(b[i]=f[i]-S[i]^2\) ,所以从图像角度看,就是找某个点使得这条直线经过它的时候算出来的 \(b\) 最小。
由上面的式子可以知道,这条直线的斜率是固定的。那么想象在一个平面上,有一些点,一条直线向上移,碰到的第一个点一定是使得 \(b\) 最小的位置。 可以发现,可能的点位于点集的下凸包上。
那么现在只需要考虑如何维护凸包点集即可。
用单调队列维护:
(1) 在凸包上找到最优点 \(j\) ,并用来更新 \(f[i]\)
(2) 将 \(i\) 作为一个决策点加入,并更新凸包(如果点 \(i\) 也是决策点之一,那么交换顺序)。具体操作为,(对于下凸包)如果 \((q[t-1],q[t])\) 的斜率不大于 \((q[t],i)\) 的斜率,那么队尾出队,出队完成后把 \(i\) 加入。
slope(q[t-1],q[t])>=slope(q[t-1],i)
(这个地方讲义里面貌似有误,和下凸包的情况不符)
决策单调性再优化
Warning :此部分需要证明,并非通用。
设 \(j_0[i]\) 为 \(f[i]\) 转移的最优决策点,那么有 \(\forall i\leq i',j_0[i]\leq j_0[i']\) (非严格递增)(证明略,见文末讲义)
考虑如何证明。此题中 \(k_0[i]=2S[i]\) 显然单增。详细一点就是:\(k_0[i]\) 单增,最优决策点单增(看下凸包的图)。当然如果不敢肯定的话还是老老实实二分栈吧qwq
由于最优决策点递增,那么可以用单调队列维护。在原先找 \(j\) 的过程中,是从队头一个一个找(或者二分)的,现在就改为:
如果队头线段斜率 \(\leq k_0[i]\) 直接出队,停止时即为最优决策点。复杂度 \(O(nlogn)=>O(n)\)
代码
#include <bits/stdc++.h>
#define ll long long
#define lb long double
using namespace std;
const int N=5e4+10;
ll n,L,h=1,t=0,q[N],s[N],f[N];
ll X( ll num ) { return s[num]; }
ll Y( ll num ) { return f[num]+(s[num]+L)*(s[num]+L); }
lb slope( ll n1,ll n2 ) { return (lb)(Y(n2)-Y(n1))/(X(n2)-X(n1)); }
int main()
{
scanf( "%lld%lld",&n,&L ); L++; s[0]=0;
for ( int i=1; i<=n ;i++ )
scanf( "%lld",&s[i] ),s[i]+=s[i-1]+1;
q[++t]=0;
for ( int i=1; i<=n; i++ )
{
while ( h<t && slope(q[h],q[h+1])<=2*s[i] ) h++;
f[i]=f[q[h]]+(s[i]-s[q[h]]-L)*(s[i]-s[q[h]]-L);
while ( h<t && slope(q[t-1],q[t])>=slope(q[t-1],i) ) t--;
q[++t]=i;
}
printf( "%lld",f[n] );
}
1——关于单调性的研究
\(X(j)\) 单增单减
将方程变为 \(\dfrac{Y(j_2)-Y(j1)}{X(j_2)-X(j_1)}\leq k_0[i]\) (或者大于等于)或者 \(kx+b=y\) 的形式。
注意遵循之前的原则。
决策点横坐标 \(X(j)\) 不单调
假设此时 \(k_0[i]\) 仍然单调。
那么维护凸包不能用单调队列了(因为会插入到点集中间某一个位置)。需要用到 平衡树维护 或者 CDQ分治 。
斜率 \(k_0[i]\) 不单调
仍然可以队列维护,但是不再满足之前的决策单调性。那么只能使用优化前的方法,二分队列找最优决策点。
以上二者均不单调
考虑在第二种情况上进行改进。
由于平衡树支持查询前驱后继,直接 \(k_0[i]\) 扔进去即可。
再看看 CDQ;在 第二种情况上再加一维偏序,人为排出单调性,普通单调队列维护即可。
好了。东西讲完了。上菜——
2——[NOIP2018PJ] P5017 摆渡车
题意
有 \(n\) 名同学要乘坐摆渡车从 \(A\) 到 \(B\),第 \(i\) 位同学在第 \(t_i\) 分钟去等车。有一辆摆渡车,容量无限大,从 \(A\) 出发、 把车上的同学送到 \(B\)、再回到 \(A\)(去接其他同学),往返一趟总共花费 \(m\) 分钟(上下车时间忽略不计)。要将所有同学都送到 \(B\).
已知你可以任意安排摆渡车出发的时间,求所有人等车时间之和的最小值。摆渡车回到 \(A\) 后可以即刻出发。
\(n\leq 500,m\leq 100,0\leq t\leq 4e6\)
思路
设 \(f_i\) 表示到了时间点 \(i\),所有同学等待时间最小和。可以写出方程:
前缀优化。设 \(cnt_i\) 为 \(i\) 时刻到达车站的学生个数,\(sum_i\) 表示已经到达车站的学生到达时间总和。有方程:
看到了 \(i,j\) 乘积!好,上斜优。首先来整理一下式子:
把 \(min\) 去掉,转化成 \(kx+b=y\) 的形式:
把 \(i\) 作为 \(k\) ,\(cnt_j\) 作为 \(x\),剩余为 \(b\) ,得到了直线方程。决策单调性转移即可。
但是这题还有一些问题需要处理:
- \(j\leq i-m\) 。采用分层入队,每当完成了 \(f_i\) 的转移,就把 \(i-m+1\) 这个决策点入队即可。
- 有重点。也就是横坐标相等纵坐标不等,那么按位置判断 \(inf/-inf\) 即可。
- \(f[]\) 需要初始化。 是第一个问题的延伸,因为前面 \(m\) 个点没有初始值。暴力更新即可。
代码
#include <bits/stdc++.h>
#define ll long long
#define lb long double
using namespace std;
const ll T=9e6+10,inf=1e18;
ll n,m,mxt,cnt[T],sum[T],f[T],q[T],h,t,ans=inf;
lb Y( int num ) { return f[num]+sum[num]; }
lb X( int num ) { return cnt[num]; }
lb slope( int x,int y )
{
if ( X(x)==X(y) ) return Y(y)>Y(x) ? inf*1.0 : inf*-1.0;
return ( Y(y)-Y(x)) / ( X(y)-X(x) );
}
int main()
{
scanf( "%lld%lld",&n,&m );
for ( ll i=1,ti; i<=n; i++ )
{
scanf( "%lld",&ti ),mxt=max( ti,mxt );
sum[ti]+=ti; cnt[ti]++;
}
for ( ll i=1; i<=mxt+m; i++ )
sum[i]+=sum[i-1],cnt[i]+=cnt[i-1];
h=t=1; q[t]=0;
for ( int i=1; i<m; i++ ) f[i]=cnt[i]*i-sum[i];
for ( int i=m; i<=mxt+m; i++ )
{
while ( h<t && slope(q[h],q[h+1])<=1.0*i ) h++;
f[i]=f[q[h]]+(cnt[i]-cnt[q[h]])*i-(sum[i]-sum[q[h]]);
while ( h<t && slope(q[t-1],q[t])>=slope(q[t],i-m+1) ) t--;
q[++t]=i-m+1;
if ( i>=mxt ) ans=min( ans,f[i] );
}
printf( "%lld",ans );
}
3——任务安排(套餐)
题目链接
LOJ10184
AcWing300
P2365
任务安排1
LOJ10186
AcWing302
P5785
任务安排3
注:此处范围、AC代码均以 AcWing 为准。
题意
有 \(N\) 个任务等待完成(顺序不改变),这 \(N\) 个任务被分成若干批,每批包含相邻的若干任务。从时刻 \(0\) 开始,这些任务被分批加工,第 \(i\) 个任务单独完成所需的时间是 \(Ti\) 。只有一台机器,在每批任务开始前,机器需要启动时间 \(S\),完成这批任务所需的时间是各个任务需要时间的总和(同一批任务将在同一时刻完成)。每个任务的费用是它的完成时刻乘以它的费用系数 \(Ci\)。请确定一个分组方案,使得总费用最小。
T1
\(1⩽N⩽5000,0⩽S⩽50,1⩽T_i,C_i⩽100\)
令 \(sumT[i]=\sum_{j=1}^i T[j],sumC[i]=\sum_{j=1}^i F[j]\),那么
考虑费用提前计算。每次分出一批任务,对后面的每个任务的用时都会产生 \(S\) 的贡献,那么可以提前计算。
代码
#include <bits/stdc++.h>
using namespace std;
const int N=5010;
int n,s,t[N],c[N],f[N];
int main()
{
scanf( "%d%d",&n,&s );
for ( int i=1; i<=n; i++ )
scanf( "%d%d",t+i,c+i );
memset( f,0x3f,sizeof f );
f[0]=0;
for ( int i=1; i<=n; i++ )
t[i]+=t[i-1],c[i]+=c[i-1];
for ( int i=1; i<=n; i++ )
for ( int j=0; j<i; j++ )
f[i]=min( f[i],f[j]+(c[i]-c[j])*t[i]+s*(c[n]-c[j]) );
printf( "%d\n",f[n] );
}
T2
\(1\leq N\leq 3e5\) \(1\leq T_i,C_i\leq 512,0\leq S\leq 512\)
\(N\) 变大了,需要加上斜率优化。
考虑转化成斜率式子。
代码
#include <bits/stdc++.h>
#define ll long long
#define lb long double
using namespace std;
const int N=3e5+10;
int n,s,q[N],h=0,t=0;
ll sc[N],st[N],f[N];
lb slope( int x,int y ) { return (lb)(f[y]-f[x])/(sc[y]-sc[x]); }
int main()
{
scanf( "%d%d",&n,&s );
for ( int i=1; i<=n; i++ )
{
scanf( "%lld%lld",&st[i],&sc[i] );
st[i]+=st[i-1]; sc[i]+=sc[i-1];
}
for ( int i=1; i<=n; i++ )
{
while ( h<t && slope(q[h],q[h+1])<=( st[i]+s ) ) h++;
f[i]=f[q[h]]-( st[i]+s )*sc[q[h]]+sc[i]*st[i]+s*sc[n];
while ( h<t && slope(q[t-1],q[t])>=slope(q[t],i) ) t--;
q[++t]=i;
}
printf( "%lld",f[n] );
}
T3
\(1\leq N\leq 3e5,0\leq S,C_i\leq 512,|T_i|\leq 512\)
任务的执行时间 \(t\) 可能是负数,那么斜率不具有单调性,
就不能只保留大于 \(S+sumT[i]\) 的部分,而应该维护整个凸壳
此时队头不一定是最优决策,需要进行二分查找,求出一个位置,
使左侧的斜率小于 \(S+sumT[i]\) ,右侧斜率大于 \(S+sumT[i]\)
注:此题 AcWing 上数据较强,建议把 slope
改为交叉相乘,需要使用 __int128
或者 doulbe
.
代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N=3e5+10;
int n,s,q[N],h=0,t=0;
ll sc[N],st[N];
double f[N];
int main()
{
scanf( "%d%d",&n,&s );
for ( int i=1; i<=n; i++ )
{
scanf( "%lld%lld",&st[i],&sc[i] );
st[i]+=st[i-1]; sc[i]+=sc[i-1];
}
for ( int i=1; i<=n; i++ )
{
int l=h,r=t;
while ( l<r )
{
int mid=(l+r)/2;
if ( (f[q[mid+1]]-f[q[mid]])>(st[i]+s)*(sc[q[mid+1]]-sc[q[mid]]) ) r=mid;
else l=mid+1;
}
f[i]=f[q[l]]-( st[i]+s )*sc[q[l]]+sc[i]*st[i]+s*sc[n];
while ( h<t && ( f[q[t]]-f[q[t-1]] )*( sc[i]-sc[q[t]] )>=( f[i]-f[q[t]] )*( sc[q[t]]-sc[q[t-1]] ) ) t--;
q[++t]=i;
}
printf( "%.0lf",f[n] );
}
中场休息
例题完成了qwq。习题部分由于太多,一天之内无法整理完,所以是选做。(是随便挑了几道,不是挑了几道好题emmm)
4——P2900 [USACO08MAR]Land Acquisition G
题意
将 \(n\) 块土地分组,每组的价格是这组土地中最大的长宽乘积,问买下所有土地的最小花费。
思路
没想到随手一点挑到了一道有意思的题。
如果你啥都不干的话,dp方程:
\(5e4\) 的 N,显然 T飞了。但是你又觉得这个区间最大很难维护。怎么办呢?
考虑一块土地到底该怎么算。显然,对于一块地 \(x\) ,如果存在一个 \(y\) 的长宽均大于它,那么这块土地跟没有一样。所以可以把所有土地按长度排序,长度相同宽度排序。维护一个栈,将土地依次加入,每次加入的时候把所有宽度小于等于它的删除即可。
此时留下的土地宽度降序。显然在最优决策下,每组土地是连续的一段。
那么终于可以得到正确的方程:
\(O(n^2)\) 还是过不去。看到 \(w[j+1]\times l[i]\) 考虑斜优。
代码
#include <bits/stdc++.h>
#define ll long long
#define lb long double
using namespace std;
const int N=5e4+10;
struct land
{
ll x,y;
bool operator < ( const land tmp )
{ if ( x!=tmp.x ) return x<tmp.x; return y<tmp.y; }
}a[N],sta[N];
int n,top=0,q[N],h,t;
ll f[N];
void init()
{
sort( a+1,a+1+n ); top++; sta[top]=a[1];
for ( int i=2; i<=n; i++ )
{
while ( top && sta[top].y<=a[i].y ) top--;
top++; sta[top]=a[i];
}
n=top;
}
lb slope( int x,int y ) { return (lb)(f[y]-f[x])/(-sta[y+1].y+sta[x+1].y); }
int main()
{
scanf( "%d",&n );
for ( int i=1; i<=n; i++ )
scanf( "%lld%lld",&a[i].y,&a[i].x );
init();
h=t=1; q[1]=0;
for ( int i=1; i<=n; i++ )
{
while ( h<t && slope(q[h],q[h+1])<=1.0*sta[i].x ) h++;
f[i]=f[q[h]]+sta[q[h]+1].y*sta[i].x;
while ( h<t && slope(q[t-1],q[t])>=slope(q[t],i) ) t--;
t++; q[t]=i;
}
printf( "%lld",f[n] );
}
5——P2120 [ZJOI2007]仓库建设
题意
有 \(n\) 个工厂,由高到低分布在一座山上,工厂 \(1\) 在山顶,工厂 \(n\) 在山脚。第 \(i\) 个工厂目前有成品 \(p_i\) 件,在第 \(i\) 个工厂位置建立仓库的费用是 \(c_i\). 对于没有建立仓库的工厂,其产品被运往其他的仓库,产品只能往山下运(只能运往编号更大的工厂的仓库),一件产品运送一个单位距离的费用是 \(1\).假设建立的仓库容量都足够大。工厂 \(i\) 与 \(1\) 的距离是 \(x_i\),问总费用最小值。
思路
前缀和,设 \(sp_i=\sum_{j=1}^i p_j,s_i=\sum_{j=1}^i p_j\times x_j\)
得到新的式子:
然后斜率优化:
代码
#include <bits/stdc++.h>
#define ll long long
#define lb long double
using namespace std;
const int N=1e6+10;
int n,q[N],h,t;
ll x[N],p[N],c[N],f[N],sp[N],s[N];
ll X( int num ){ return sp[num]; }
ll Y( int num ){ return f[num]+s[num]; }
lb slope( int n1,int n2 ) { return (lb)(Y(n2)-Y(n1))/(X(n2)-X(n1)); }
int main()
{
scanf( "%d",&n );
for ( int i=1; i<=n; i++ )
scanf( "%lld%lld%lld",&x[i],&p[i],&c[i] );
sp[0]=s[0]=0;
for ( int i=1; i<=n; i++ )
sp[i]=sp[i-1]+p[i],s[i]=s[i-1]+p[i]*x[i];
h=t=1; q[1]=0;
for ( int i=1; i<=n; i++ )
{
while ( h<t && slope(q[h],q[h+1])<=(lb)x[i] ) h++;
f[i]=f[q[h]]+x[i]*(sp[i]-sp[q[h]])-(s[i]-s[q[h]])+c[i];
while ( h<t && slope(q[t-1],q[t])>=slope(q[t],i) ) t--;
t++; q[t]=i;
}
printf( "%lld",f[n] );
}
6——P3628 [APIO2010]特别行动队
题意
有一支由 \(n\) 名士兵组成的部队,士兵从 \(1\) 到 \(n\) 编号,要将他们拆分成若干个特别行动队调入战场。同一支行动队的队员的编号应该连续。
编号为 \(i\) 的士兵的初始战斗力为 \(x_i\) ,一支队伍的初始战斗力为所有队员初始战斗力之和,记为 \(x\) 。最终战斗力为:\(x'=ax^2+bx+c.(a<0)\).
试求出最终战斗力之和的最大值。
思路
令 \(s[i]=\sum_{j=1}^i x_j.\)
所以整合得到:
代码
#include <bits/stdc++.h>
#define ll long long
#define lb long double
using namespace std;
const int N=1e6+10;
int n,q[N],h,t;
ll s[N],a,b,c,f[N];
ll X( int num ){ return s[num]; }
ll Y( int num ){ return f[num]+a*s[num]*s[num]-b*s[num]; }
lb slope( int n1,int n2 ) { return (lb)(Y(n2)-Y(n1))/(X(n2)-X(n1)); }
int main()
{
scanf( "%d",&n ); scanf( "%lld%lld%lld",&a,&b,&c );
s[0]=0;
for ( int i=1; i<=n; i++ )
scanf( "%lld",&s[i] ),s[i]+=s[i-1];
h=t=1; q[1]=0;
for ( int i=1; i<=n; i++ )
{
while ( h<t && slope(q[h],q[h+1])>=(lb)2.0*a*s[i] ) h++;
int j=q[h]; f[i]=f[j]+a*s[j]*s[j]-b*s[j]+a*s[i]*s[i]+b*s[i]+c-2*a*s[i]*s[j];
while ( h<t && slope(q[t-1],q[t])<=slope(q[t],i) ) t--;
t++; q[t]=i;
}
printf( "%lld",f[n] );
}
7——P4027 [NOI2007]货币兑换
又是 AcWing 众多恶评题之一(
题意
太长了,自己看题
又是用NOI题作结的一天呢
本题没有部分分,你的程序的输出只有和标准答案相差不超过 \(0.0010.001\) 时,才能获得该测试点的满分,否则不得分。
输入文件可能很大,请采用快速的读入方式。
必然存在一种最优的买卖方案满足:
每次买进操作使用完所有的人民币,每次卖出操作卖出所有的金券。
思路
这道题就是典型的不能单调栈,要平衡树或者 CDQ 的题目 就算是早年NOI也没那么简单呢qaq
设 \(f[i]\) 表示到第 \(i\) 天最多有多少钱,\(g[i]\) 表示用第 \(i\) 天的钱最多能买多少 \(B\) 券。易知 \(g[i]=\dfrac{f[i]}{r[i]\times a[i]+b[i]}\)
得到转移:
外面的 \(max\) 单独判断即可。主要是斜优里面那个式子。
但是你发现斜率不单调。所以根据前文所述,要使用平衡树或者CDQ分治维护了。
由于平衡树 太长 太板子了,这里就不作介绍,采用 CDQ分治维护。
CDQ分治比较好的总结可以看这里,顺便我的LCT也是这个博主教的
现在 假装你已经会CDQ分治了 考虑分治,对于任意一个 \(f[i]\) ,只需要考虑 \(1\leq j\leq i-1\) 即可,和CDQ的解决方式非常相像。
对于一段区间 \([l,r]\) ,先递归左子区间 \([l,m]\) 保证 \([l,m]\) 的 \(f,g\) 都已经得到;把左子区间按 \(k\) 递增排序,就可以按斜率优化 \(O(n)\) 转移;再把右子区间按在原序列中的位置递增排序,递归。此时左子区间对右子区间的影响已经考虑完毕。注意边界 \(l==r\) 。复杂度是 \(O(nlog^2n).\)
继续优化。考虑 CDQ本身就是类似归并排序的过程,可以用这个去掉排序复杂度。
我们希望得到什么呢?当我们拿到 \([l,r]\) 这个区间的时候,\(x\) 是单调的。于是把外面的原序列按 \(x\) 递增排序;拿到这个序列之后,我们希望在原序列中靠左的东西去左子区间,把 \([l,r]\) 扫一遍,把在原序列中 \(\leq m\) 的东西放左边,\(\ge m\) 的东西放右边,而且左右区间对 \(x\) 的单调性没有影响。在递归右子区间的时候,希望左子区间回来的时候关于 \(k\) 单调递增,所以最后对 \(k\) 做一遍归并。复杂度 \(O(nlogn).\)
代码
写了将近半个晚上啊啊啊啊啊
#include <bits/stdc++.h>
using namespace std;
const int N=1e5+10;
const double eps=1e-8;
int q[N],n;
double f[N], g[N];
struct node
{
int id; double a,b,r,x;
node() {}
node( int id,double a,double b,double r ) : id(id),a(a),b(b),r(r),x(b/a) {}
bool operator < (node tmp ) { return x!=tmp.x ? x<tmp.x : id<tmp.id; }
}p[N],b[N];
double cross( int u,int v )
{
return (p[u].r*g[p[u].id]-p[v].r*g[p[v].id])/(g[p[v].id]-g[p[u].id]);
}
double calc( int u,int v ) { return g[p[u].id]*(p[v].x+p[u].r); }
void update( int u,double v )
{
if ( f[p[u].id]<v ) f[p[u].id]=v,g[p[u].id]=f[p[u].id]/(p[u].b+p[u].r*p[u].a);
}
void solve( int l,int r )
{
if ( l==r ) { update(l,f[p[l].id-1]); return; }
int m=(l+r)>>1,i,h,t;
for ( h=l,t=m+1,i=l; i<=r; i++ )
p[i].id<=m ? b[h++]=p[i] : b[t++]=p[i];
for ( int i=l; i<=r; i++ )
p[i]=b[i];
solve( l,m ); h=1; t=0;
for ( i=l; i<=m; i++ )
{
while ( h<t && cross(q[t],i)<cross(q[t-1],i)+eps ) t--;
q[++t]=i;
}
for ( ; i<=r; i++ )
{
while ( h<t && calc( q[h],i )<calc( q[h+1],i )+eps ) h++;
update( i,calc(q[h],i)*p[i].a );
}
solve( m+1,r );
for ( h=l,t=m+1,i=l; h<=m && t<=r; )
g[p[h].id]<g[p[t].id] ? b[i++]=p[h++] : b[i++]=p[t++];
while ( h<=m ) b[i++]=p[h++];
while ( t<=r ) b[i++]=p[t++];
for ( i=l; i<=r; i++ ) p[i]=b[i];
}
int main()
{
scanf( "%d%lf",&n,&f[1] );
double a,b,r;
for ( int i=1; i<=n; i++ )
scanf( "%lf%lf%lf",&a,&b,&r ),p[i]=node(i,a,b,r);
g[1]=f[1]/(p[1].r*p[1].a+p[1].b); sort( p+1,p+1+n );
solve( 1,n );
printf( "%.3lf\n",f[n] );
}
8——注意事项
- 写出DP方程之后要判断能不能用斜优
不要无脑使用啊,即是否存在 \(g(i)\times g(j)\) 或者 \(\dfrac{Y(j)-Y(j')}{X(j)-X(j')}\) 的形式。 - 通过大于小于符号或者 \(b\) 中的 \(f[i]\) 结合要求 \(min/max\) 判断上下凸包,不要盲猜
- 当 \(X(j)\) 非严格递增时,在求斜率时可能会出现相等的情况,这时候一定要特判(除数不能为0),
return Y(j)>=Y(i) ? inf : -inf
,不要直接返回。 - 比较 \(k_0[i],slope(j_1,j_2)\) 要写规范,可能出现同除负数却没有变号。
- 队列初始化要塞一个点 \(P(0),j=0\)
- 出入队列的判断要至少两个元素(不然没有斜率),所以不能写
h<=t
,应该写h<t
. - 计算斜率的除法有误差,用
long double
- 有可能出现部分dp初始值无法转移,需要预处理(如摆渡车)
- 比较两个斜率的时候尽量写
<=,>=
以避免有重点时分母出问题。但保险起见建议加上第三条中的判断。
Last——鸣谢
和上一篇一样,是对 @[Xing_Ling]
这篇博文
的学习整理。同样适合作为初学教材。