HDU-3401:Trade(dp+單調佇列優化)
阿新 • • 發佈:2019-01-08
題目大意:
就是有一個人知道每一天的股票的購入和賣出的價錢(注意只有一種股票),並且每天能夠購入和賣出的股票的數量也是有限制的。第 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);
}
}