1. 程式人生 > >[多維DP] UVa1412 基金管理(九元組及編解碼)(狀態池)

[多維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的數量不是

98而是<5107(可以由組合數學算出)。
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;
}