[斜率優化入門][HNOI2008]玩具裝箱/[BZOJ3156]防禦準備/[NOI2007]貨幣兌換

斜率優化主要針對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;
}

初次接觸的基礎理解吧。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章