1. 程式人生 > >動態規劃的單調佇列優化(含多重揹包)

動態規劃的單調佇列優化(含多重揹包)

什麼是單調佇列

單調佇列就是元素單調的佇列,譬如一個佇列中的元素為1,2,3,4,5,6,單調遞增,這就是一個單調佇列。咱們先看一道單調佇列的模板題:poj2823/洛谷P1886
怎麼維護單調佇列呢?譬如維護一個單調遞增的佇列,就是要進入一個元素的時候,把隊尾小於它的元素統統出隊即可。而在例題中,我們還要記錄每個元素在原來陣列中的下標以確定是否可用,如果已經出了當前視窗,則出隊。
程式碼:

void getmin(){//單調遞增
    int he=1,ta=1,i;
    for(i=1;i<=n;i++){
        while(he<ta&&q[ta-1
]>=a[i])ta--; q[ta]=a[i];bj[ta]=i;ta++; if(i>=m){ while(he<ta&&bj[he]<=i-m)he++; printf("%d ",q[he]); } } } void getmax(){//單調遞減 int he=1,ta=1,i; for(i=1;i<=n;i++){ while(he<ta&&q[ta-1]<=a[i])ta--; q[ta]=a[i];bj[ta]=i;ta++; if
(i>=m){ while(he<ta&&bj[he]<=i-m)he++; printf("%d ",q[he]); } } }

單調佇列優化動態規劃

例題1:洛谷P1725 琪露諾

連結:走你╭(′▽`)╯
這題就當是單調佇列入門啦。
大家都知道f[i]=max(f[j])+v[i](ir<=j<=il)
直接這麼dp肯定超時,那麼我們可以把f[ir]f[il]這一段都扔到單調佇列裡,然後取隊首即可
單調佇列一定不能刪除還有可能用到的元素,也不能新增暫時不會用的元素,所以我們要確保在用單調佇列時,i

l加了進去,沒有被其後的元素刪掉,而其後的東西也沒有加進去。
所以就有了程式碼中的寫法
程式碼:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<map>
#include<cmath>
using namespace std;
#define ll long long
ll read(){
    ll q=0,w=1;char ch=' ';
    while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
    if(ch=='-')w=-1,ch=getchar();
    while(ch>='0'&&ch<='9')q=q*10+(ll)(ch-'0'),ch=getchar();
    return q*w;
}
const int maxn=200005;
int n,l,r;
ll v[maxn],f[maxn],ans;
int bj[maxn];
int main()
{
    int i,j,ta=1,he=1;ll kl;
    n=read();l=read();r=read();
    for(i=0;i<=n;i++)v[i]=read();
    f[0]=v[0];
    for(i=l;i<=n;i++){
        while(he<ta&&f[bj[ta-1]]<=f[i-l])ta--;
        bj[ta]=i-l;ta++;
        while(he<ta&&bj[he]<i-r)he++;
        f[i]=f[bj[he]]+v[i];
        if(i>=n-r)ans=max(ans,f[i]);
    }
    printf("%lld",ans);
    return 0;
}

例題2:UESSTC594我要長高

連結:走你╭(′▽`)╯
這題充滿了惡意啊……
容易想到用f[i][j]表示第i個兒子長j這麼高的時候的最小損失值(x[i]表示i兒子本來的身高,則:

f[i][j]=min(f[i1][k]+abs(jk)c+(x[i]j)(x[i]j))
現在我們分類討論一下,假如j>k:
f[i][j]=min(f[i1][k]+(jk)c+(x[i]j)(x[i]j)
變形可得:f[i][j]=min((f[i1][k]kc)+(jc+(x[i]j)(x[i]j)))
顯然前面那一坨可以塞在一個單調佇列裡來求小於j的情況下的最優k,具體怎麼實現看程式碼吧。然後j<k的情況也差不多:
f[i][j]=min((f[i1][k]+kc)+(jc+(x[i]j)(x[i]j)))
得到了美妙的程式碼:
#include<iostream>
#include<cstdio>
#include<climits>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
int read(){
    int q=0;char ch=' ';
    while(ch<'0'||ch>'9')ch=getchar();
    while(ch>='0'&&ch<='9')q=q*10+ch-'0',ch=getchar();
    return q;
}
const int maxn=105;
int n,c,inf=0x3f3f3f3f;
int f[2][maxn],q[maxn];
int main()
{
    int x,i,j,t,ans,he,ta;
    while(scanf("%d%d",&n,&c)==2){
        x=read();t=1;
        for(i=0;i<x;i++)f[t][i]=inf;
        for(i=x;i<=100;i++)f[t][i]=(x-i)*(x-i);
        for(i=2;i<=n;i++){
            t=i&1;x=read();
            he=ta=1;
            for(j=0;j<=100;j++){//比前一個人高,顯然弄到j的時候k取0~j-1的情況都已討論過
                int kl=f[t^1][j]-j*c;
                while(he<ta&&q[ta-1]>=kl)ta--;
                q[ta]=kl;ta++;
                if(j<x)f[t][j]=inf;
                else f[t][j]=q[he]+j*c+(x-j)*(x-j);
            }
            he=ta=1;
            for(j=100;j>=0;j--){//比前一個人矮,顯然弄到j的時候k取j+1~100的情況都已討論過
                int kl=f[t^1][j]+j*c;
                while(he<ta&&q[ta-1]>=kl)ta--;
                q[ta]=kl;ta++;
                if(j<x)f[t][j]=inf;
                else f[t][j]=min(f[t][j],q[he]-j*c+(x-j)*(x-j));
            }
        }
        t=n&1;ans=inf;
        for(i=0;i<=100;i++)ans=min(ans,f[t][i]);
        printf("%d\n",ans);
    }
    return 0;
}

例題3:HDU3401

連結:走你╭(′▽`)╯
題目大意是買股票,第i天買花ap[i]元,賣得bp[i]元,可以買as[i]張或者賣bs[i]張,兩次交易之間必須間隔w天,並且手上最多持有m股,求最多賺多少錢。
f[i][j]表示第i天持有j股的最多收益,
如果不交易,那麼f[i][j]=f[i1][j]
如果買f[i][j]=f[iw1][k](jk)ap[i]
如果賣f[i][j]=f[iw1][k]+(kj)bp[i]
(因為不交易的狀態已經轉移了,所以買和賣只要考慮第iw1天即可)
然後我們把狀態轉移方程變形一下,就是
買:f[i][j]=(f[iw1][k]+kap[i])jap[i]
賣:f[i][j]