1. 程式人生 > >HDU 3401 Trade(用單調佇列優化DP)

HDU 3401 Trade(用單調佇列優化DP)

題意:給出T天內,每天的股票買賣價格和每天的股票買賣最大數量,而且最多隻能擁有maxP數量的股票,開始時有無限本金,任意兩次交易需要間隔W天及以上,也就是第i天交易,第i+w+1天及以後才能再交易。最多能賺多少錢?(題意應該還有一個隱晦的要求是,當天只能買股票或者賣股票,不能同時做)

思路:很明顯的DP問題,用dp[i][j]表示第i天擁有j數量股票時的最多賺錢數。容易得到狀態轉移方程:

(1)當天不買不賣:

dp[i][j] = max(dp[i][j],dp[i - 1][j])

(2)買股票:

dp[i][j] = max(dp[i][j],dp[h][k] - (j - k)* APi[i]),1 <= h <= i - W - 1,0 <= k <= j

(3)賣股票:

dp[i][j] = max(dp[i][j],dp[h][k] +(k - j)* BPi[i]),1 <= h <= i - W - 1,j < k <= maxP

可以看出,這個狀態轉移方程變數太多,不便於處理,因此必須進行優化:

優化一:h必需的嗎?否

由狀態轉移方程(1),第i - W - 1天可以看作前一天不買不賣得到的,依次類推,可以看出,之前的都不需要考慮了,只考慮第i - W - 1天的情況即可。

則:

    買股票:

    dp[i][j] = max(dp[i][j],dp[i - W -1][k] - (j - k)* APi[i]),0 <= k <= j

    賣股票:

    dp[i][j] = max(dp[i][j],dp[i - W -1][k] +(k - j)* BPi[i]),j < k <= maxP

優化二:優化一後,現在仍需要列舉i、j、k三個量,O(n ^ 3)的複雜度,容易超時。以(2)為例,dp[i][j] = max(dp[i][j],dp[i - W - 1][k] - (j - k)* APi[i])= max(dp[i][j],dp[i - W -1][k] + k * APi[i] - j * APi[i]),符合單調佇列優化DP的基本形式:dp[i] = max / min (f[k])+ g[i]  (k < i && g[i]是與k無關的變數),這樣通過單調佇列對dp方程進行優化,將O(n)複雜度降至O(1)(均攤),減少k這層列舉,使複雜度達到O(n ^ 2)。

// HDU 3401 Trade 執行/限制:218ms/1000ms
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
using namespace std;
#define INF 0X3f3f3f3f
int T, maxP, W;//總天數 可擁有股票最多數量
int APi[2005],BPi[2005],ASi[2005],BSi[2005];//當天買股票的價格 當天賣股票的價格 當天最多買的股票 當天最多賣的股票
int dp[2005][2005];
struct node {
	int num;//股票數量
	int m;//dp[i - W - 1][k] + k * Api[i]或者 dp[i - W - 1][k] + k * BPi[i]
};
node queue[2005];
int head, tail;
int main(){
	int t;
	scanf("%d", &t);
	while (t--) {
		scanf("%d%d%d", &T, &maxP, &W);
		for (int i = 1; i <= T; i++) {
			scanf("%d%d%d%d", &APi[i], &BPi[i], &ASi[i], &BSi[i]);
		}
		//初始化dp陣列
		for (int i = 0; i <= T; i++) {
			for (int j = 0; j <= maxP; j++) {
				dp[i][j] = -INF;
			}
		}
		//預處理前W+1天
		for (int i = 1; i <= W + 1; i++) {
			for (int j = 0; j <= min(maxP,ASi[i]); j++) {//買股票
				dp[i][j] = -j*APi[i];
			}
		}
		dp[0][0] = 0;
		for (int i = 1; i <= T; i++) {//天數
			for (int j = 0; j <= maxP; j++) {//第i天不買不賣
				dp[i][j] = max(dp[i][j], dp[i - 1][j]);
			}
			if (i - W - 1 <= 0) continue;
			int pre = i - W - 1;
			//買股票
			head = 1;
			tail = 0;
			for (int j = 0; j <= maxP; j++) {//股票數量
				int f = dp[pre][j] + APi[i] * j;
				while (head <= tail && queue[tail].m <= f) {
					tail--;
				}
				queue[++tail].num = j;
				queue[tail].m = f;
				while (head <= tail && queue[head].num + ASi[i] < j) {
					head++;
				}
				dp[i][j] = max(dp[i][j], queue[head].m - APi[i] * j);
			}
			//賣股票
			head = 1;
			tail = 0;
			for (int j = maxP; j >= 0; j--) {//股票數量
				int f = dp[pre][j] + BPi[i] * j;
				while (head <= tail && queue[tail].m <= f) {
					tail--;
				}
				queue[++tail].num = j;
				queue[tail].m = f;
				while (head <= tail && queue[head].num - BSi[i] > j) {
					head++;
				}
				dp[i][j] = max(dp[i][j], queue[head].m - BPi[i] * j);
			}
		}
		int re = -INF;
		for (int i = 0; i <= maxP; i++) {
			re = max(re, dp[T][i]);
		}
		printf("%d\n", re);
	}
    return 0;
}