[单调队列DP] HDU3401 Trade

Trade
题意:
炒股,给出第1~t天每天的买入价格Ap[i]和卖出价格Bp[i],每天最多能买入的数量As[i]和最多能卖出的数量Bs[i]。
还有几个限制,任意时刻最多持有Pmax数量的股票,两次交易(买入或卖出)间隔至少w天,如第i天交易,那么下次至少是i+w+1天。问你第t天最大收入。
题解:
首先考虑朴素DP。
DP[i][j] 表示第i天持有j股票时的最大收入。
讨论转移方程:
第i天啥也不干:
DP[i][j]=DP[i1][j]
第i天买点,显然有 j>k
DP[i][j]=DP[iw1][k](jk)Ap[i],(jk<=As[i])
第i天卖点,显然有 k>j
DP[i][j]=DP[iw1][k]+(kj)Bp[i],(kj<=Bs[i])
然后每天能干的事就考虑完了,接下来考虑代码实现。

for(int i = 1; i <= t; ++i){
    for(int j = 0; j <= p; ++j){
        int mx = -inf;
        mx = max(dp[i][j], dp[i-1][j]);
        if(i <= w+1) continue;
        for(int k = j; k >= 0 && j-k <= As[i]; --k){
            mx = max(mx, dp[i-w-1][k]-(j-k)*Ap[i]);
        }
        for(int k = j; k <= p && k-j <= Bs[i]; ++k){
            mx = max(mx, dp[i-w-1][k]+(k-j)*Bp[i]);
        }
        dp[i][j] = mx;
    }
}

我写出来是这样的,这是个 tpp DP ,显然过不了。
那就要想优化了。
第一个方程是 O(1) 的就不说了。
看第二个方程。
DP[i][j]=DP[iw1][k](jk)Ap[i]
整理一下。
DP[i][j]=DP[iw1][k]+kAp[i]jAp[i]
再令 f[iw1][k]=DP[iw1][k]+kAp[i]
那么方程可以这样转化:
DP[i][j]=f[iw1][k]jAp[i]
那么转移完的结果就是
DP[i][j]=max(f[iw1][k])jAp[i],(j>k)
单调队列就是针对这样的方程进行优化的。
一般的,对于类似 DP[i]=max/min(f[k])+C[j] ,其中 Ck ,都可以使用单调队列,效果是使 O(N) 的转移降至 O(1)
代码实现上,根据维护的信息范围不同,遍历的顺序就不同。

#include<stdio.h>
#include<algorithm>
#include<string.h>
using namespace std;
const int inf = ~0u>>2;
const int N = 2005;
int dp[N][N]; // 第i天拥有j股票情况下的最大收入
// dp[i][j] = dp[i-1][j];
// dp[i][j] = dp[i-w-1][k]+k*Api-j*Api; j <= p && j-k <= Asi 买入
// dp[i][j] = dp[i-w-1][k]+k*Bpi-j*Bpi; j >= 0 k-j <= Bsi 卖出
//
// 令 f[i-w-1][k] = dp[i-w-1][k]+k*Api
// dp[i][j] = max(f[i-w-1][k]) - j*Api; j > k
//
// 令f[i-w-1][k] = dp[i-w-1][k]+k*Bpi
// dp[i][j] = max(f[i-w-1][k]) - j*Bpi j < k
int As[N], Bs[N], Ap[N], Bp[N];
int top, tail;
struct node{
    int val, pos;
    node(){}
    node(int a, int b){ val = a, pos = b;}
}q[N*10];
int main(){
    int T;
    scanf("%d", &T);
    while(T--){
        int t, p, w;
        scanf("%d%d%d", &t, &p, &w);
        for(int i = 1; i <= t; ++i) scanf("%d%d%d%d", Ap+i, Bp+i, As+i, Bs+i);
        for(int i = 0; i <= t; ++i) for(int j = 0; j <= p; ++j) dp[i][j] = -inf;
        for(int i = 1; i <= w+1; ++i){
            int up = min(p, As[i]);
            for(int j = 0; j <= up; ++j){
                dp[i][j] = -j*Ap[i];
            }
        }
        for(int i = 1; i <= t; ++i){
            top = tail = 0;
            for(int j = 0; j <= p; ++j){
                dp[i][j] = max(dp[i][j], dp[i-1][j]);
                if(i <= w+1) continue;
                int nf = dp[i-w-1][j]+j*Ap[i];
                while(top < tail && q[tail-1].val < nf) tail--;
                q[tail++] = node(nf, j);
                while(top < tail && j-q[top].pos > As[i]) top++;
                dp[i][j] = max(dp[i][j], q[top].val-j*Ap[i]);
            }
            top = tail = 0;
            for(int j = p; j >= 0; --j){
                dp[i][j] = max(dp[i][j], dp[i-1][j]);
                if(i <= w+1) break;
                int nf = dp[i-w-1][j]+j*Bp[i];
                while(top < tail && q[tail-1].val < nf) tail--;
                q[tail++] = node(nf, j);
                while(top < tail && q[top].pos-j > Bs[i]) top++;
                dp[i][j] = max(dp[i][j], q[top].val-j*Bp[i]);
            }
        }
        int ans = 0;
        for(int i = 0; i <= p; ++i) ans = max(ans, dp[t][i]);
        printf("%d\n", ans);
    }
}

你可能感兴趣的:(dp,单调队列)