斜率優化主要針對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;
}
初次接觸的基礎理解吧。