1D1D動態規劃優化初步

再學習一下動態規劃的基本優化方法…ios

首先這篇文章應該你們都看過吧…沒看過的自行百度函數

關於實現的思路文章裏都給好了…這篇就主要給一點題目啥的學習

(P.S. 電腦重裝了,若是博客發出來有一些奇怪的問題不要在乎)優化

模型一,即決策單調性優化spa

①玩具裝箱 bzoj10103d

題目本身看去code

用dp[x]表示裝前x個的最小費用,sum[x]表示C的前綴和。blog

能夠發現dp[i]=min{dp[j]+(i-j+sum[i]-sum[j]-1-L)^2} (0<=j<i)隊列

這樣彷佛仍是不夠美觀,咱們令p[i]=i+sum[i],g=L+1。get

dp[i]=min{dp[j]+(p[i]-p[j]-g)^2} (0<=j<i)

美觀了一點…

這樣硬肛是O(n^2)的,感受很不茲磁啊…

首先:四邊形不等式

當函數w(i,j)知足w(a,c)+w(b,d)<=w(b,c)+w(a,d)且a<=b<c<=d時咱們稱w(i,j)知足四邊形不等式。

假如咱們用w(j,i)表示(p[i]-p[j]-g)^2,那麼(打表)能夠發現w是知足四邊形不等式的。

什麼?講道理?

w(a,c)+w(b,d)-w(b,c)-w(a,d)=2g(p[c]-p[a]+p[d]-p[b]-p[c]+p[b]-p[d]+p[a])+p[a]^2+p[c]^2+p[b]^2+p[d]^2-p[b]^2-p[c]^2-p[a]^2-p[d]^2-2p[a]p[c]-2p[b]p[d]+2p[b]p[c]+2p[a]p[d](展開)=-2p[a]p[c]-2p[b]p[d]+2p[b]p[c]+2p[a]p[d](容易觀察出剩下的都爲0)=2(p[d]-p[c])(p[a]-p[b])<0(因爲p顯然單調增)。

模型一 image且w知足四邊形不等式

因爲決策單調性,咱們能夠知道若是a<b<c<d且在c處選a比選b優,那麼在d處選a也比選b優。

因此咱們能夠用一個單調隊列(單調棧)來維護決策,每有一個新決策咱們就用二分維護隊列最優性,隊列每個元素維護對於哪些狀態(必定是一個連續的區間)當前這個決策時最優的,而後隊列從尾到頭一個一個元素二分當前狀態哪裏最優,而後暴力彈出就行。

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <algorithm>
using namespace std;
typedef long long ll;
#define SZ 233333
int n,l; ll p[SZ],dp[SZ];
int sn=0,sa[SZ],sl[SZ],sr[SZ];
ll wd(int j,int i) {return dp[j]+(p[i]-p[j]-l)*(p[i]-p[j]-l);}
int main()
{
    scanf("%d%d",&n,&l); ++l;
    ll sum=0;
    for(int i=1;i<=n;i++)
    {
        int c; scanf("%d",&c);
        sum+=c; p[i]=sum+i;
    }
    sn=1; sa[1]=0; sl[1]=1; sr[1]=n;
    for(int i=1;i<=n;i++)
    {
        int jc=sa[upper_bound(sl+1,sl+1+sn,i)-sl-1];
        dp[i]=wd(jc,i);
        while(sn&&wd(sa[sn],sl[sn])>=wd(i,sl[sn])) --sn;
        if(!sn) {sn=1; sa[1]=i; sl[1]=1; sr[1]=n; continue;}
        int l=sl[sn],r=sr[sn]; //到l爲止原決策更優
        while(l<r)
        {
            int mid=l+r+1>>1;
            if(wd(sa[sn],mid)<wd(i,mid)) l=mid; else r=mid-1;
        }
        if(l==n) continue;
        sr[sn]=l; ++sn; sa[sn]=i;
        sl[sn]=l+1; sr[sn]=n;
    }
    printf("%lld\n",dp[n]);
}

愉快的O(nlogn)。

②土地購買 bzoj1597

購買一些土地,能夠分組購買,每組土地的價格是最大長*最大寬,求最小費用。

例如1*5和5*1兩塊地顯然要分2組購買。

咱們考慮把長度從高到低排個序,這樣咱們就只要考慮寬度就行啦。

咱們用l表示長度,w表示寬度好了,那麼

image

咱們發現這個玩意兒根本不知足決策單調性…

咱們回想一下以前作的某一題(其實我也忘了是哪一題),只要把徹底沒有用,會被徹底包含的點刪掉就是單調的了。

咱們發現若是點(l,w)存在另外一個點(l',w')且l<=l',w<=w',那麼(l,w)必定能夠在選(l',w')的時候被順便選掉,不會影響總代價。

咱們考慮先將l排好序後,用一個單調棧同樣的東西來維護一下w,顯然是線性的。

以後的方程就是單調的了。

(這裏咱們改用x表示長度,y表示寬度,懶得改公式了)

image

仍是能夠像上一題同樣講道理!

咱們令w(k,n)爲x[n]*y[k+1],那麼

w(a,c)+w(b,d)-w(b,c)-w(a,d)=(x[b]-x[a])*(y[d+1]-y[c+1])。

因爲x不減y不增顯然<=0。

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <algorithm>
using namespace std;
#define X first
#define Y second
#define SZ 666666
typedef pair<int,int> pii;
typedef long long ll;
pii ps[SZ];
int n,sn=0,ss[SZ];
bool dd[SZ];
ll dp[SZ];
int jn=0,jl[SZ],jr[SZ],jc[SZ],nxt[SZ];
ll w(int a,int b) {return ps[b].X*(ll)ps[nxt[a]].Y+dp[a];}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d%d",&ps[i].X,&ps[i].Y);
    sort(ps+1,ps+1+n);
    for(int i=1;i<=n;i++)
    {
        while(sn&&ps[ss[sn]].Y<=ps[i].Y) dd[ss[sn--]]=1;
        ss[++sn]=i;
    }
    jn=1; jc[1]=0; jl[1]=1; jr[1]=n;
    int lst=0;
    for(int i=1;i<=n;i++)
    {
        if(!dd[i]) nxt[lst]=i, lst=i;
    }
    for(int i=1;i<=n;i++)
    {
        if(dd[i]) continue;
        int t=upper_bound(jl+1,jl+1+jn,i)-jl-1;
        dp[i]=w(jc[t],i);
        while(jn&&w(jc[jn],jl[jn])>=w(i,jl[jn])) --jn;
        if(!jn) {jn=1; jc[1]=i; jl[1]=1; jr[1]=n; continue;}
        int l=jl[jn],r=jr[jn];
        while(l<r)
        {
            int mid=l+r+1>>1;
            if(w(jc[jn],mid)<w(i,mid)) l=mid; else r=mid-1;
        }
        if(l==n) continue;
        jr[jn]=l; ++jn; jc[jn]=i;
        jl[jn]=l+1; jr[jn]=n;
    }
    printf("%lld\n",dp[n]);
}

模型二,即單調隊列優化

這個最典型的就是有限揹包(多重揹包)

好比咱們有若干個物品,價值爲v[i],重量爲w[i],數量限制爲s[i]。

咱們考慮一個傻逼dp:

f[i][j]=max{f[i-1][j-x*w[i]]+x*v[i]} (0<=x<=s[i],x*w[i]<=j)

而後咱們發現這個x*w[i]<=j不太和諧…

咱們考慮把j對模w[i]的餘數進行分類!

設j=q*w[i]+p,那麼

f[i][p][q]=max{f[i-1][p][x]+(q-x)*v[i]} (0<=x<=q且x>=q-s[i])

因此咱們發現能夠直接上單調隊列優化!

就是說咱們能夠用一個單調隊列來維護決策,隊伍裏前面的決策比後面的必定優秀。

每一次咱們在隊尾加新的決策,而後維護隊列的單調性。

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
using namespace std;
#define SZ 666666
int n,tw;
typedef long long ll;
ll f[SZ];
void pack01(ll w,ll v)
{
    for(int i=tw;i>=w;i--) f[i]=max(f[i],f[i-w]+v);
}
void packfull(ll w,ll v)
{
    for(int i=w;i<=tw;i++) f[i]=max(f[i],f[i-w]+v);
}
//強行二進制分解 
void packmulti2(ll w,ll v,int c)
{
    if(w>tw||c==0) return;
    if(c==1) {pack01(w,v); return;}
    if(w*c>=tw) {packfull(w,v); return;}
    for(int k=1;k<c;k<<=1) pack01(w*k,v*k), c-=k;
    pack01(w*c,v*c);
}
int qs[SZ],*qh[SZ],*qt[SZ];
ll nf[SZ];
//聽說比較優越的單調隊列 
void packmulti1(ll w,ll v,int c)
{
    if(w>tw||c==0) return;
    if(c==1) {pack01(w,v); return;}
    if(w*c>=tw) {packfull(w,v); return;}
    int pss=tw/w+1,*cur=qs;
    for(int i=0;i<w;i++) qh[i]=qt[i]=cur, cur+=pss;
    for(int i=0;i<=tw;i++)
    {
        int bl=i%w; ll cv=f[i]-i/w*v;
        while(qh[bl]!=qt[bl]&&*qh[bl]<i-c*w) ++qh[bl];
        while(qh[bl]!=qt[bl]&&f[*(qt[bl]-1)]-*(qt[bl]-1)/w*v<cv) --qt[bl];
        *(qt[bl]++)=i;
        nf[i]=f[*qh[bl]]+(i/w-(*qh[bl])/w)*v;
    }
    for(int i=0;i<=tw;i++) f[i]=nf[i];
}
int main()
{
    scanf("%d%d",&n,&tw);
    for(int i=1;i<=n;i++)
    {
        int w,v,m;
        scanf("%d%d%d",&w,&v,&m);
        if(m==1) pack01(w,v);
        else if(m==-1) packfull(w,v);
        else packmulti2(w,v,m);
    }
    printf("%lld\n",f[tw]);
}

不知道是否是我寫狗了…單調隊列作法跑的特別慢…一臉懵逼

相關文章
相關標籤/搜索