1. 程式人生 > >HDU-3401:Trade(dp+單調佇列優化)

HDU-3401:Trade(dp+單調佇列優化)

題目大意:

就是有一個人知道每一天的股票的購入和賣出的價錢(注意只有一種股票),並且每天能夠購入和賣出的股票的數量也是有限制的。第 i 天買的股票也只能第 i+w+1 天賣出。但是無論何時,他手中持有的股票的數量不能超過m、問他本金無限的情況下最多盈利多少。

題意解析:

很容易分析出來是一道dp題目 每一天的狀態可以由他 i-w-1 天前的狀態推出來,再列舉他今天 買入 或 賣出 多少股票。dp轉移方程就不說了。來說一下這道題需要用到的一個技巧,單調佇列優化 dp 。

單調佇列優化dp,剛開始著實理解不來 。看大佬程式碼一頭霧水。遂決定先去寫個超時程式碼。果然超時程式碼寫出來之後對比著就比較容易理解了。

單調佇列dp 適用於  dp[i]=max/min (f[k]) + g[i]  (k<i && g[i]是與k無關的變數)(借用一下大佬的)這種情況。

接下來談我自己對單調佇列的理解,當然不一定正確,自己瞎猜的。因為 k 是與 i 有關的,理論上來說 我們若想得出正確答案,最暴力的方法是列舉完 i 再列舉 k 。這樣的話就是一個雙重迴圈。但是單調佇列可以幫助我們一重迴圈解決。就是直接將 i 列舉一遍。列舉 i 的同時也同時將 k 作為 i 列舉。但是因為 k 和 i 之間有某種關係 。所以我們需要將枚舉出來的 i 先儲存起來,維護一個單調佇列。那麼對於你列舉到後面的 i 的時候,這個單調佇列裡面的頭結點對應的 i 一定是最優解並且他將作為 k 參加dp轉移方程。這樣就可以一重迴圈完美的解決問題了。不得不說真的神奇。

下面貼兩段程式碼,一個超時,一個是優化過的。對比著看的話應該更容易理解。

/* 優化版 */
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <vector>
#include <queue>
#include <map>
#include <algorithm>
#include <set>
#include <functional>
#define lson rt<<1
#define rson rt<<1|1
using namespace std;
typedef long long ll;
const int INF = 1e9 + 5;
int n,m,w,ans;
struct node
{
    int idx;
    int kk;
};
int ap[2005],bp[2005];
int as[2005],bs[2005];
int dp[2005][2005];
node q[2005];   //單調佇列
int main()
{
    int QAQ;
    scanf("%d",&QAQ);
    while(QAQ--)
    {
        scanf("%d%d%d",&n,&m,&w);
        for(int i=1;i<=n;i++)
            scanf("%d%d%d%d",&ap[i],&bp[i],&as[i],&bs[i]);
        for(int i=0;i<=n;i++)
            for(int j=0;j<=m;j++)
            dp[i][j]=-INF;
        for(int i=1;i<=w+1;i++)        //預處理前w+1天的情況
            for(int j=0;j<=min(as[i],m);j++)
            dp[i][j]=-j*ap[i];
        dp[0][0]=0;
        int head=0,tail=0;
        for(int i=1;i<=n;i++)
        {
            for(int j=0;j<=m;j++)
                dp[i][j]=max(dp[i][j],dp[i-1][j]);  //不買不賣的情況
            if(i<=w+1)
                continue ;
            int g=i-w-1,gg;
            head=0;tail=0;
            for(int j=0;j<=m;j++)   //買入情況 列舉 j 
            {
                gg=dp[g][j]+j*ap[i];
                while(head<tail&&q[tail-1].kk<gg)   // 將j不斷加入單調佇列並維護
                    tail--;
                q[tail].idx=j;q[tail++].kk=gg;
                while(head<tail&&q[head].idx+as[i]<j)   //將前面部分不符合要求的點去掉
                    head++;
                dp[i][j]=max(dp[i][j],q[head].kk-j*ap[i]);      //當前最優狀態轉移方程
            }
            head=0;tail=0;
            for(int j=m;j>=0;j--)   //賣出 同上
            {
                gg=dp[g][j]+j*bp[i];
                while(head<tail&&q[tail-1].kk<gg)
                    tail--;
                q[tail].idx=j;q[tail++].kk=gg;
                while(head<tail&&q[head].idx-bs[i]>j)
                    head++;
                dp[i][j]=max(dp[i][j],q[head].kk-j*bp[i]);
            }
        }
        ans=0;
        for(int i=0;i<=m;i++)
            ans=max(ans,dp[n][i]);
        printf("%d\n",ans);
    }
}


/* 未優化版 */
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <vector>
#include <queue>
#include <map>
#include <algorithm>
#include <set>
#include <functional>
#define lson rt<<1
#define rson rt<<1|1
using namespace std;
typedef long long ll;
const int INF = 1e9 + 5;
int n,m,w,ans;
int ap[2005],bp[2005];
int as[2005],bs[2005];
int dp[2005][2005];
int q[2005];
int main()
{
    int QAQ;
    scanf("%d",&QAQ);
    while(QAQ--)
    {
        scanf("%d%d%d",&n,&m,&w);
        for(int i=1;i<=n;i++)
            scanf("%d%d%d%d",&ap[i],&bp[i],&as[i],&bs[i]);
        for(int i=0;i<=n;i++)
            for(int j=0;j<=m;j++)
            dp[i][j]=-INF;
        for(int i=1;i<=w+1;i++)
        {
            for(int j=0;j<=min(as[i],m);j++)
                dp[i][j]=-j*ap[i];
        }
        dp[0][0]=0;
        for(int i=1;i<=n;i++)
        {
            for(int j=0;j<=m;j++)
                dp[i][j]=max(dp[i][j],dp[i-1][j]);
            if(i<=w+1)
                continue ;
            int g=i-w-1;
            for(int j=m;j>=0;j--)
                for(int k=0;k+as[i]<j;k++)      //重點看k的範圍 單調佇列要用到
                    dp[i][j]=max(dp[g][k]-(j-k)*ap[i],dp[i][j]);
            for(int j=0;j<=m;j++)
                for(int k=min(j+bs[i],m);k>j;k--)       //同上
                    dp[i][j]=max(dp[g][k]+(k-j)*bp[i],dp[i][j]);
        }
        ans=0;
        for(int i=0;i<=m;i++)
            ans=max(ans,dp[n][i]);
        printf("%d\n",ans);
    }
}