斜率优化主要针对dp
当遇到dp[i]=min(a[i]*dp[j]+f[j]+b[i]+j) 此种类似情况时(也就是对于求i 我们要考虑选哪个j以致最值),考虑斜率优化
具体步骤: 考虑此时k优于j (k>j)
那么 a[i]*dp[k]+f[k]+b[i]+k<a[i]*dp[j]+f[j]+b[i]+j
把i放在一边,j,k,放在一边 若dp[]单调递增 即-a[i]>(k+f[k]-j-f[j])/(k-j)时满足条件
将其考虑到平面上的点对A(a,a+f[a])、B(b,b+f[b])(a>b)
只有当AB的斜率<-a[i] a优于b
在此凸包中斜率单调上升, 而对于一个查询-a[i],我们要找到的,是不存在一个点k(<i && >j)能使kj斜率<-a[i],同时也不存在一个点k(<i && <j)能使kj斜率>-a[i]的点j,也就是说 斜率为-a[i]的直线与凸包切点(考虑凸包内的点一定不满足如上性质)
考虑对于每一个 i 都有一个固定的对应点
此时只需维护凸包即可
很多时候,根据题意,a[i]单调上升或单调下降,此时我们只要用队列维护一个凸包即可 (具体见图)
(凸包斜率单调,我们查询在队首,对于每一个i 维护当前凸包,新增点i在队尾,对于新增点,加入凸包,若前一点到此点的斜率<前一点到前一点的前一点的斜率,则说明前一点不在凸包内,将此点从队列尾部弹出)
[HNOI2008]玩具装箱/[BZOJ3156]防御准备 就都是这样的
以[BZOJ3156]防御准备 为例吧
#include<bits/stdc++.h>
using namespace std;
long long tmp,h,t,n,q[2000001],a[2000001],f[2000001];
long long zhi(long long a) { return f[a]+(a*(a+1))/2;}
double slop(long long a,long long b)
{ return 1.0*(zhi(a)-zhi(b))/(a-b);}
int main()
{
cin>>n;
for (int i=1;i<=n;i++) cin>>a[i];
h=t=0;
for (long long i=1;i<=n;i++)
{
while (h<t && slop(q[h+1],q[h])<=i) h++;
tmp=q[h];
f[i]=zhi(tmp)-i*tmp+a[i]+(i*(i-1)>>1);
while (h<t && slop(i,q[t])<=slop(q[t],q[t-1])) {t--;}
q[++t]=i;
}
cout<<f[n]<<endl;
}/*一定要注意for循环里的i乘法运算爆int*/
那么a[i]不是单调的呢
[NOI2007]货币兑换
先考虑方程
易想 一天只可能全部买入或全部卖出
公式部分见qiqi课件
此时用cdq分治维护
对于后一半的值,可以用前一半的点构成的凸包对其进行更新
凸包的合并只需要按x的大小归并就行了(思路就是这样,感到抽象看代码)
#include<bits/stdc++.h>
using namespace std;
#define db double
#define inf 1e9
#define eps 1e-9
const int N=100005;
int n,s[N];db dp[N];
struct node{db k,x,y,a,b,r;int id;}Q[N],kl[N];
db getk(int i,int j) {
if(fabs(Q[i].x-Q[j].x)<=eps) return inf;
return (Q[j].y-Q[i].y)/(Q[j].x-Q[i].x);
}
void merge(int l,int r,int mid) {//归并排序
int t1=l,t2=mid+1;
for(int i=l;i<=r;++i)
if(t1<=mid&&(t2>r||Q[t1].x<Q[t2].x+eps)) kl[i]=Q[t1],++t1;
else kl[i]=Q[t2],++t2;
for(int i=l;i<=r;++i) Q[i]=kl[i];
}
void cdq(int l,int r) {
if(l==r) {//那么在l之前的所有询问都已经处理完毕,可以更新l的答案了
dp[l]=max(dp[l],dp[l-1]);
Q[l].y=dp[l]/(Q[l].a*Q[l].r+Q[l].b),Q[l].x=Q[l].y*Q[l].r;
return;
}
int mid=(l+r)>>1,t1=l-1,t2=mid,top=0;
for(int i=l;i<=r;++i)//把前mid个询问放在左边,后mid个放在右边
if(Q[i].id<=mid) kl[++t1]=Q[i];
else kl[++t2]=Q[i];
for(int i=l;i<=r;++i) Q[i]=kl[i];
cdq(l,mid);//递归处理左边
for(int i=l;i<=mid;++i) {//维护斜率递减的凸包
while(top>=2&&getk(s[top],i)+eps>getk(s[top-1],s[top])) --top;
s[++top]=i;
}
for(int i=mid+1;i<=r;++i) {//处理右边的询问
while(top>=2&&getk(s[top-1],s[top])<=Q[i].k+eps) --top;
int j=s[top];
dp[Q[i].id]=max(dp[Q[i].id],Q[j].x*Q[i].a+Q[j].y*Q[i].b);
}
cdq(mid+1,r),merge(l,r,mid);//递归处理右边后,按照x值为关键字归并排序
}
int cmp1(node t1,node t2) {return t1.k<t2.k;}
int main()
{
scanf("%d%lf",&n,&dp[0]);
for(int i=1;i<=n;++i) {
scanf("%lf%lf%lf",&Q[i].a,&Q[i].b,&Q[i].r);
Q[i].k=-Q[i].a/Q[i].b,Q[i].id=i;
}
sort(Q+1,Q+1+n,cmp1),cdq(1,n);
printf("%.3lf\n",dp[n]);
return 0;
}
初次接触的基础理解吧。