题意
思路:
本题的基本思路是明确的,用d(i,p)表示经过 i 天之后,资产组合为p时的现金的最大值。
另外值得注意的是,本题在考虑买股票时要考虑到当前拥有的现金是否足够,因此不是一个DAG最长/最短路问题,因为某些边u->v的存在性依赖于起点到点u的最短路值。也就是说,本题不能像之前的DAG问题一样“反着定义”:如果用d(i,p)表示资产组合为p,从第 i 天开始到最后能拥有的现金最大值,会发现状态根本无法转移。
- 9进制处理资金组合状态
- 状态编号,不必编码解码
- 刷表法
#include <cstdio>
#include <vector>
#include <map>
#include <algorithm>
using namespace std;
const int INF = 1<<30;
const int maxn = 8;
const int maxm = 101;
const int maxstate = 15000;
double c, price[maxn][maxm];
int m, n, kk;
char name[maxn][50];
int s[maxn], k[maxn];
vector< vector<int> > states; // states[i]:状态 i
map< vector<int>, int > ID; // 状态对应的编号
// 为每种状态编号
void dfs(int cur_stock, vector<int> &s, int total){
if(cur_stock == n){
ID[s] = states.size();
states.push_back( s );
return;
}
// 对当前股票买 i 手
for(int i = 0; i <= k[cur_stock] && total + i <= kk; ++i){
s[cur_stock] = i;
dfs(cur_stock + 1, s, total + i);
}
}
int buy_next[maxstate][maxn], sell_next[maxstate][maxn]; // 分别表示状态 s 进行"买股票i”和“卖股票i”之后转移到的状态编号
void init(){
vector<int> s1(n);
states.clear();
ID.clear();
// 给所有股票状态编号
dfs(0, s1, 0);
// 求buy_next, sell_next
for(int s = 0; s < states.size(); ++s){
int total = 0;
for(int i = 0; i < n; ++i) total += states[s][i];
for(int i = 0; i < n; ++i){
buy_next[s][i] = sell_next[s][i] = -1;
// 买第 i支股票
if(states[s][i] < k[i] && total < kk){
vector<int> newstate = states[s];
++newstate[i];
buy_next[s][i] = ID[newstate];
}
// 卖第 i支股票
if(states[s][i] > 0){
vector<int> newstate = states[s];
--newstate[i];
sell_next[s][i] = ID[newstate];
}
}
}
}
double d[maxm][maxstate];
int opt[maxm][maxstate], pre[maxm][maxstate];
// 当前时间(day)的状态是s,它的操作op可能给下一个状态s2带来影响,如果它的决策好,及时更新
void update(int day, int s, int s2, double v, int op){
if(v > d[day+1][s2]){
d[day+1][s2] = v;
opt[day+1][s2] = op;
pre[day+1][s2] = s;
}
}
// 规划
double dp(){
// 初始化
for(int day = 0; day <= m; ++day)
for(int s = 0; s < states.size(); ++s)
d[day][s] = -INF;
//printf("%d\n",states.size());
d[0][0] = c;
for(int day = 0; day < m; ++day){
for(int s = 0; s < states.size(); ++s){
double v = d[day][s];
if(v < -1) continue;
// hold
update(day, s, s, v, 0);
for(int i = 0; i < n; ++i){
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); // buy ith stock
if(sell_next[s][i] >= 0)
update(day, s, sell_next[s][i], v + price[i][day], -i-1); // sell ith stock
}
}
}
return d[m][0];
}
void print_ans(int day, int s){
if(day == 0) return;
print_ans(day-1, pre[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 cnt = 0;
void test(int cur, int total){
if(cur == 8){
++cnt;
return;
}
for(int i = 0; i <= 8 && total+i <= 8; ++i){
test(cur+1, total+i);
}
}
*/
int main()
{
//freopen("in.txt","r",stdin);
// test(0,0);
// printf("%d\n",cnt); 输出12870,方案总数不超过15000.
int kase = 0;
while(scanf("%lf %d %d %d",&c, &m, &n, &kk) == 4){
if(kase++ > 0) printf("\n");
for(int i = 0; i < n; ++i){
scanf("%s %d %d",name[i], &s[i], &k[i]);
for(int j = 0; j < m; ++j) {
scanf("%lf",&price[i][j]);
price[i][j] *= s[i];
}
}
init();
double ans = dp();
printf("%.2lf\n", ans);
print_ans(m, 0);
}
return 0;
}