昨天在HDU上看到的这题 : HDU 3401 Trade
题意 : 一个人一开始有很多钱,然后买股票,总共天数是T天,然后如果在i天买进(卖出)一部分股票的话,下一次操作至少要在W+1天后了,然后告诉你这只股票每天的买进和卖出价格,每天最多买进和卖出的股票数有一定的限制,一个人最多拥有的股票是MaxP,问你最后一天最多能够赚多少钱。
思路 : 其实转移方程感觉很裸,令DP[i][j]表示第i天拥有j只股票所能够获得的最大价值,然后DP[i][j] = max(dp[i-1][j],dp[i-W-1][k] + F(买进或者卖出一部分)); 其中DP[i-W-1]k]来源有两部分,一部分是k < j然后在i天时买进(j-k)只,即DP[i-W-1][k] - (j - k) * buy[i] ,另一部分是k > j 在i 天卖出了(k - j)只,即DP[i-W-1][k] + (k - j) * sell[i];
思路很清晰,但是因为朴素的DP的话复杂度在O(Maxp * T * T),这道题是肯定爆的。
后来看到有种叫做单调队列的东西,然后学了下,切了一道水题 : poj 2823 Sliding Window。
单调队里做法 : 考虑在i 天买进了一些stocks : DP[i][j] = max(dp[i][j],dp[i-W-1][k] - (j - k) * buy[i]);
将这个状态转移方程变下形 : DP[i][j] = max(dp[i][j],{dp[i-W-1][k] + k * buy[i]} - j * buy[i]);
因为在考虑(i,j)这个状态时j * buy[i] 是不会变的,我们需要的就是找到一个k使得它的P(k) = DP[i-W-1][k] + k * buy[i]最大,也就是简化成了 : 在 k 属于 [j-i天最大能买多少stocks , j)这个范围下的最大P(k),所以我们只要开一个单调队里来维护即可,队首元素最大。同理另一种也是如此。
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int INF = 9999999;
const int MAXN = 2005;
int n,W,MaxP,buy[MAXN],sell[MAXN],MaxB[MAXN],MaxS[MAXN];
int que[MAXN],tot[MAXN],head,tail;
int dp[MAXN][MAXN];
void init()
{
for (int i = 0;i <= n;i++)
for (int j = 0;j <= MaxP;j++)
dp[i][j] = -INF;
for (int i = 1;i <= W+1;i++)
for (int j = 0;j <= min(MaxP,MaxB[i]);j++)
dp[i][j] = - buy[i] * j;
}
int solve()
{
init();
for (int i = 1;i <= n;i++)
{
for (int j = 0;j <= MaxP;j++)
dp[i][j] = max(dp[i-1][j],dp[i][j]);
if (i <= W + 1)continue;
int cur = i - W - 1;
head = tail = 0;
for (int k = 0;k <= MaxP;k++)
{
while (head != tail && que[tail] < dp[cur][k] + k * buy[i]) tail--;
que[++tail] = dp[cur][k] + k * buy[i]; tot[tail] = k;
while (tot[head+1] < k - MaxB[i]) head++;
dp[i][k] = max(dp[i][k],que[head+1] - k * buy[i]);
}
head = tail = 0;
for (int k = MaxP;k >= 0;k--)
{
while (head != tail && que[tail] < dp[cur][k] + k * sell[i]) tail--;
que[++tail] = dp[cur][k] + k * sell[i]; tot[tail] = k;
while (tot[head+1] > k + MaxS[i]) head++;
dp[i][k] = max(dp[i][k],que[head+1] - k * sell[i]);
}
}
int ans = -INF;
for (int i = 0;i <= MaxP;i++)ans = max(ans,dp[n][i]);
return ans;
}
int main()
{
int T;
scanf("%d",&T);
for (int cas = 1;cas <= T;cas++)
{
scanf("%d%d%d",&n,&MaxP,&W);
for (int i = 1;i <= n;i++)
{
scanf("%d%d%d%d",&buy[i],&sell[i],&MaxB[i],&MaxS[i]);
}
printf("%d\n",solve());
}
return 0;
}
另外单调队列还可以优化多重背包,不过我觉得多重背包问题二进制优化就已经差不多了。