[多維DP] UVa1412 基金管理(九元組及編解碼)(狀態池)
題目
思路
本題的基本思路是明確的,用d(i,p)表示經過i天之後,資產組合為p時的現金的最大值。
另外值得注意的是,本題在考慮買股票時要考慮到當前擁有的現金是否足夠,因此不是一個DAG最長/最短路問題,因為某些邊u->v的存在性依賴於起點到點u的最短路值。也就是說,本題不能像之前的DAG問題一樣“反著定義”:如果用d(i,p)表示資產組合為p,從第i天開始到最後能擁有的現金最大值,會發現狀態根本無法轉移。這個點跟價值不變換的烹調方案有類似。
本題的重點就落在了p的表示方式上面。
首先應該知道的是,p是一個九元組,p[i]表示第i只股票持有的手數。並且由於存在最大持有股票手數k(k最大為8),所以p的數量不是
LRJ介紹了兩種方法,第一種用一個九進位制整數來表示p,通過解碼,編碼兩步操作來進行狀態的轉移。優點是普遍性較強,缺點是無法直接對狀態轉移,必須先進行解碼,編碼,增加了時間複雜度。
第二種是使用狀態池,也就是實現計算出所有可能的狀態並編號,甚至在構造一個狀態轉移表,把每一個狀態的所有轉移都預處理地清清楚楚。優點是減少了時間複雜度。
本題作為一個NOI難度的題目,重點在於通過程式碼對LRJ所說兩種方法的理解,所有應該仔細地一行一行讀程式碼。
程式碼
九進位制整數表示。(會TLE)
對程式碼中,變數的解釋:
c:初始資金;m:天數;n:股數;kk:最大總持股手數。
name[i]:第i股的名字;s[i]:第i股一手有多少股;k[i]:第i股最大持有手數。
price[i][j]:第i只股,第j天每手股的股價。
d[i][p]:第i天,資產情況為p,的最大剩餘資金。
opt[i][p]:第i天,資產情況為p,這天執行的動作。
myprev[i][p]:第i天,資產情況為p,這天還沒有執行任何動作時的資產情況。
#include <cstdio>
#include <cstring>
#include <map>
#define _for(i,a,b) for(int i = (a); i<(b); i++)
#define _rep(i,a,b) for(int i = (a); i<=(b); i++)
using namespace std;
const double INF = 1e30;
const int maxn = 8;
const int maxm = 100 + 5;
// 本題變數含義詳見csdn blog
map<int, double > d[maxm]; // d採用map形式的原因是:完全d[][]會佔用過多記憶體,實際用不到這麼多d
map<int, int> opt[maxm], myprev[maxm];
int m, n, s[maxn], k[maxn], kk;
double c, price[maxn][maxm];
char name[maxn][10];
// 九元組程式碼編碼函式
int encode(int *portfolio) {
int h = 0;
_for(i, 0, n) h = h * 9 + portfolio[i];
return h;
}
// 九元組程式碼解碼函式,h為程式碼,portfolio為解碼後陣列,返回值為總股票手數
int decode(int h, int *portfolio){
int totlot = 0;
for (int i = n - 1; i >= 0; i--) {
portfolio[i] = h % 9;
totlot += portfolio[i];
h /= 9;
}
return totlot;
}
// update為記錄函式,每當創造一個新的d值時,可以再次處記錄,o為動作
void update(int oldh, int day, int h, double v, int o) {
if (d[day].count(h) == 0 || v > d[day][h]) {
d[day][h] = v;
opt[day][h] = o;
myprev[day][h] = oldh;
}
}
double dp() {
int portfolio[maxn]; // 九元組
d[0][0] = c;
_for(day, 0, m) {
for (map<int, double>::iterator iter = d[day].begin(); iter != d[day].end(); ++iter) {
int h = iter->first; // 九元組程式碼
double v = iter->second; // 剩餘資金
int totlot = decode(h, portfolio);
update(h, day + 1, h, v, 0); // HOLD
_for(i, 0, n) {
if (portfolio[i] < k[i] && totlot < kk && v >= price[i][day] - 1e-3) { // 目的未知的精度處理
portfolio[i]++;
update(h, day + 1, encode(portfolio), v - price[i][day], i + 1); // BUY
portfolio[i]--; // 還原現場
}
if (portfolio[i] > 0) {
portfolio[i]--;
update(h, day + 1, encode(portfolio), v + price[i][day], -i - 1); // SELL
portfolio[i]++; // 還原現場
}
}
}
}
return d[m][0];
}
void print_ans(int day, int h) {
if (day == 0) return;
print_ans(day - 1, myprev[day][h]);
if (opt[day][h] == 0) printf("HOLD\n");
else if (opt[day][h] > 0) printf("BUY %s\n", name[opt[day][h] - 1]);
else printf("SELL %s\n", name[-opt[day][h] - 1]);
}
int main() {
int kase = 0;
while (scanf("%lf%d%d%d", &c, &m, &n, &kk) == 4) {
if (kase++ > 0) printf("\n");
_for(i, 0, n) {
scanf("%s%d%d", name[i], &s[i], &k[i]);
_for(j, 0, m) { scanf("%lf", &price[i][j]); price[i][j] *= s[i]; }
}
_rep(i, 0, m) {
d[i].clear();
opt[i].clear();
myprev[i].clear();
}
double ans = dp(); // dp採用遞推
printf("%.2lf\n", ans);
print_ans(m, 0);
}
return 0;
}
狀態池。
對程式碼中,變數的解釋:
c:初始資金;m:天數;n:股數;kk:最大總持股手數。
name[i]:第i股的名字;s[i]:第i股一手有多少股;k[i]:第i股最大持有手數。
price[i][j]:第i只股,第j天每手股的股價。
d[i][p]:第i天,狀態序號為p,的最大剩餘資金。
opt[i][p]:第i天,狀態序號為p,這天執行的動作。
myprev[i][p]:第i天,狀態序號為p,這天還沒有執行任何動作時的狀態序號。
ID[vp]:資產情況vector對應的狀態序號。
states[p]:狀態序號p對應的資產情況vector。
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <vector>
#include <map>
#define _for(i,a,b) for(int i = (a); i<(b); i++)
#define _rep(i,a,b) for(int i = (a); i<=(b); i++)
using namespace std;
const double INF = 1e30;
const int maxn = 8;
const int maxm = 100 + 5;
const int maxstate = 15000;
int m, n, s[maxn], k[maxn], kk;
double c, price[maxn][maxm];
char name[maxn][10];
double d[maxm][maxstate];
int opt[maxm][maxstate], myprev[maxm][maxstate];
int buy_next[maxstate][maxn], sell_next[maxstate][maxn];
vector<vector<int> > states;
map<vector<int>, int> ID;
// stock:遍歷到的股票序號,lots已經寫好的持有情況,totlot總持有股票手數
void dfs(int stock, vector<int>& lots, int totlot) {
if (stock == n) {
ID[lots] = states.size();
states.push_back(lots);
}
else for (int i = 0; i <= k[stock] && totlot + i <= kk; i++) {
lots[stock] = i;
dfs(stock + 1, lots, totlot + i);
}
}
void init() {
vector<int> lots(n); // 在lots中建立n個0
states.clear();
ID.clear();
dfs(0, lots, 0);
_for(s, 0, states.size()) {
int totlot = 0;
_for(i, 0, n) totlot += states[s][i];
_for(i, 0, n) {
buy_next[s][i] = sell_next[s][i] = -1; // 考慮買賣不了的情況,所以初值-1
if (states[s][i] < k[i] && totlot < kk) {
vector<int> newstate = states[s];
newstate[i]++;
buy_next[s][i] = ID[newstate];
}
if (states[s][i] > 0) {
vector<int> newstate = states[s];
newstate[i]--;
sell_next[s][i] = ID[newstate];
}
}
}
}
void update(int day, int s, int s2, double v, int o) {
if (v > d[day + 1][s2]) {
d[day + 1][s2] = v;
opt[day + 1][s2] = o;
myprev[day + 1][s2] = s;
}
}
double dp() {
_rep(day, 0, m) _for(s, 0, states.size()) d[day][s] = -INF;
d[0][0] = c;
_for(day,0,m)
_for(s, 0, states.size()) {
double v = d[day][s];
if (v < -1) continue; // 這種狀態不可能達到
update(day, s, s, v, 0); // HOLD
_for(i, 0, n) {
if (buy_next[s][i] >= 0 && v >= price[i][day] - 1e-3)
update(day, s, buy_next[s][i], v - price[i][day], i + 1);
if (sell_next[s][i] >= 0)
update(day, s, sell_next[s][i], v + price[i][day], -i - 1);
}
}
return d[m][0];
}
void print_ans(int day, int s) {
if (day == 0) return;
print_ans(day - 1, myprev[day][s]);
if (opt[day][s] == 0) printf("HOLD\n");
else if (opt[day][s] > 0) printf("BUY %s\n", name[opt[day][s] - 1]);
else printf("SELL %s\n", name[-opt[day][s] - 1]);
}
int main() {
int kase = 0;
while (scanf("%lf%d%d%d", &c, &m, &n, &kk) == 4) {
if (kase++ > 0) printf("\n");
_for(i, 0, n) {
scanf("%s%d%d", name[i], &s[i], &k[i]);
_for(j, 0, m) { scanf("%lf", &price[i][j]); price[i][j] *= s[i]; }
}
init();
double ans = dp();
printf("%.2lf\n", ans);
print_ans(m, 0);
}
return 0;
}