[多维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的数量不是 9 8 而是 < 5 10 7 (可以由组合数学算出)。
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;
}

猜你喜欢

转载自blog.csdn.net/icecab/article/details/81018286