本题的基本思路是明确的,用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 9 8 而是 <5∗107 < 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
#include
#include
#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
#include
#include
#include
#include
#include
#include
#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;
}