[BZOJ1855][Scoi2010]股票交易(DP+单调队列优化)

f[i][j] f [ i ] [ j ] 表示到了第 i i 天,手里有j张股票,赚到最多的钱。
如果第 i i 天不进行交易,则f[i][j]可以从 f[i1][j] f [ i − 1 ] [ j ] 转移。即:

f[i][j]=f[i1][j] f [ i ] [ j ] = f [ i − 1 ] [ j ]

如果第 i i 天进行交易,则f[i][j]可以从 f[max(0,iW1)][...] f [ max ( 0 , i − W − 1 ) ] [ . . . ] 转移。
(下面设 p=max(0,iW1) p = max ( 0 , i − W − 1 )
设存在非负整数 k,h k , h 满足 k>h k > h
判断 h×BPik×APi h × B P i − k × A P i (kh)×APi − ( k − h ) × A P i 的大小关系。
把右边拆开得到 h×APik×APi h × A P i − k × A P i ,由于 APiBPi A P i ≥ B P i h>0 h > 0
因此 (kh)×APih×BPik×APi − ( k − h ) × A P i ≥ h × B P i − k × A P i
k<h k < h 时同理。因此得出:在最优情况下,一天要么不进行交易,要么只买入,要么只卖出。
所以得出第 i i 天进行交易时的转移:
f[i][j]=max(f[i][j],maxjASik<j{f[p][k](jk)×APi})

f[i][j]=max(f[i][j],maxj<kBSi+j{f[p][k]+(kj)×BPi}) f [ i ] [ j ] = max ( f [ i ] [ j ] , max j < k ≤ B S i + j { f [ p ] [ k ] + ( k − j ) × B P i } )

将等号右边 max max 里的式子拆开,得到:
maxjASik<j{f[p][k]+k×APi}j×APi max j − A S i ≤ k < j { f [ p ] [ k ] + k × A P i } − j × A P i

maxj<kBSi+j{f[p][k]+k×BPi}j×BPi max j < k ≤ B S i + j { f [ p ] [ k ] + k × B P i } − j × B P i

复杂度 O(T×MaxP2) O ( T × M a x P 2 )
考虑到 jASi j − A S i BSi+j B S i + j j j 单调递增,
因此可以用单调队列分别维护f[p][k]+k×APi f[p][k]+k×BPi f [ p ] [ k ] + k × B P i 的最大值。复杂度 O(T×MaxP) O ( T × M a x P )
代码:

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
inline int read() {
    int res = 0; bool bo = 0; char c;
    while (((c = getchar()) < '0' || c > '9') && c != '-');
    if (c == '-') bo = 1; else res = c - 48;
    while ((c = getchar()) >= '0' && c <= '9')
        res = (res << 3) + (res << 1) + (c - 48);
    return bo ? ~res + 1 : res;
}
const int N = 2005, INF = 0x3f3f3f3f;
int T, maxP, W, AP[N], BP[N], AS[N], BS[N], f[N][N], He, Ta, Q[N];
bool ex[N][N];
int main() {
    int i, j; T = read(); maxP = read(); W = read();
    for (i = 1; i <= T; i++) AP[i] = read(), BP[i] = read(),
        AS[i] = read(), BS[i] = read(); ex[0][0] = 1;
    for (i = 1; i <= T; i++) {
        for (j = 0; j <= maxP; j++) f[i][j] = -INF;
        for (j = 0; j <= maxP; j++) if (ex[i - 1][j])
            f[i][j] = f[i - 1][j], ex[i][j] = 1; int p = max(0, i - W - 1);
        He = Ta = 0; for (j = 0; j <= maxP; j++) {
            while (He < Ta && j - Q[He + 1] > AS[i]) He++;
            if (He != Ta) f[i][j] = max(f[i][j], f[p][Q[He + 1]]
                - (j - Q[He + 1]) * AP[i]), ex[i][j] = 1;
            if (ex[p][j]) {
                while (He < Ta && f[p][Q[Ta]] + Q[Ta] * AP[i] <
                    f[p][j] + j * AP[i]) Ta--; Q[++Ta] = j;
            }
        }
        He = Ta = 0; for (j = maxP; j >= 0; j--) {
            while (He < Ta && Q[He + 1] - j > BS[i]) He++;
            if (He != Ta) f[i][j] = max(f[i][j], f[p][Q[He + 1]]
                + (Q[He + 1] - j) * BP[i]), ex[i][j] = 1;
            if (ex[p][j]) {
                while (He < Ta && f[p][Q[Ta]] + Q[Ta] * BP[i] <
                    f[p][j] + j * BP[i]) Ta--; Q[++Ta] = j;
            }
        }
    }
    int ans = -INF; for (i = 0; i <= maxP; i++)
        if (ex[T][i]) ans = max(ans, f[T][i]); cout << ans << endl;
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值