题目链接
简介:
你有c美元现金,没有股票,给你m天时间和n支股票供你购买,要求最后一天结束后不持有任何股票,且剩余的钱最多
已知每支股票每天的价格和参数si和ki,表示一手股票是si股,且每天持有的手数不能超过ki,其中k为每天持有的总手数上限。每天要么不操作,要么买一支股票,买或卖ta的一手股票
要求输出每一天的决策
分析:
好像和这道题有点像bzoj1492(好像又不一样)
每天持有的股票和现金会影响下一步的决策,所以我们干脆把状态记录下来
设f[i][p]表示经过i天后,资产组合为p时的现金最大值
其中p是一个n元组,pi<=ki表示第i只股股票有pi手,
根据题目规定,p1+p2+p3+…+pn<=k
因为p最大只有8,所以我们可以用一个9进制数保存p
一共有3种决策:HOLD,BUY,SELL
注意在转移时要考虑钱到底够不够
当现在为止,我们想出来的方法都挺好,但是效率不够高,而且有一个致命的弱点:有人愿意写9进制吗
不停地在进制之间转化,是非常耗时间的
解决方式是事先计算出所有可能的状态并且编号(状压dp的经典处理):
vector<vector<int> > states;
map<vector<int>,int> ID;
void dfs(int stock,vector<int>& lots,int totlot) //totlots 当前拥有的手数
{
if (stock==n) //stock 当前需要枚举的股票种类
{
ID[lots]=states.size(); //ID是lots在vector中的编号
states.push_back(lots); //lots就是状态
}
else
for (int i=0;i<=k[stock]&&i+totlot<=K;i++)
{
lots[stock]=i;
dfs(stock+1,lots,totlot+i);
}
}
之后我们需要构造一个状态转移表,
用buy_nxt[s][i]和sell_nxt[s][i]分别表示状态s进行“买股票i”和“卖股票i”之后转移到的状态编号
int buy_nxt[maxstate][maxn],sell_nxt[maxstate][maxn];
void init()
{
vector<int> lots(n);
states.clear();
ID.clear();
dfs(0,lots,0);
for (int s=0;s//枚举状态
{
int totlot=0;
for (int i=0;i//计算当前状态的总手数
for (int i=0;i1;
if (states[s][i]vector<int> newstate=states[s];
newstate[i]++; //买一股i
buy_nxt[s][i]=ID[newstate];
}
if (states[s][i]>0)
{
vector<int> newstate=states[s];
newstate[i]--;
sell_nxt[s][i]=ID[newstate]; //买一股i
}
}
}
}
dp主体主要使用刷表法
填表法
简单来说就是:针对状态i,计算f(i)
所以我们在计算i之前需要知道i所依赖的所有状态刷表法
对于每一个状态i,更新f(i)影响到的所有状态
需要注意的是,只有状态依赖的状态之间互相独立才能用刷表法
为了方便,我们把更新状态专门放到了一个update函数中
因为题目要求打印解,所以我们需要在转移的同时更新最优策略opt以及“上一个状态”pre
double d[maxm][maxstate];
int opt[maxm][maxstate],pre[maxm][maxstate];
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; //这一次的操作
pre[day+1][s2]=s; //上一次的状态
}
}
double dp()
{
for (int day=0;day<=m;day++)
for (int s=0;s//浮点数需要逐个赋值,不能用memset
d[0][0]=c;
for (int day=0;dayfor (int s=0;sdouble v=d[day][s];
if (v<0) continue; //一定要从稳定状态转移
update(day,s,s,v,0); //HOLD
for (int i=0;i//枚举股票
{
if (buy_nxt[s][i]!=-1&&v>=price[i][day]-1e-3) //钱必须够才能buy
update(day,s,buy_nxt[s][i],v-price[i][day],i+1); //BUY 为什么非要加1呢,为了避免编号是0的情况
if (sell_nxt[s][i]!=-1)
update(day,s,sell_nxt[s][i],v+price[i][day],-i-1); //SELL
}
}
return d[m][0];
}
最后就是打印解了(递归比较方便)
void print(int day,int s)
{
if (day==0) return;
print(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]);
}
n和m和name和k都是从0开始编号的
浮点数需要逐个赋值,不能用memset
我一开始用map处理name,但是不知道为什么只能保存最后一个名字
只好换成普通的char(果然还是朴朴素素最好)
之后我的答案一直少两块钱,只能一点一点debug
最后把错误锁定在了最前面的变量声明
最后发现是一个double变量的类型搞错了,所以损失了精度
//这里写代码片
#include
#include
#include
#include
using namespace std;
const double INF=1e10;
const int maxstate=15000; //>2^7*100
const int maxn=8;
const int maxm=100+3;
int k[maxn],K,n,m;
double c,price[maxn][maxm];
char name[maxn][10];
vector<vector<int> > states;
map<vector<int>,int> ID;
void dfs(int stock,vector<int>& lots,int totlot) //totlots 当前拥有的手数
{
if (stock==n) //stock 当前需要枚举的股票种类
{
ID[lots]=states.size(); //ID是lots在vector中的编号
states.push_back(lots); //lots就是状态
}
else
for (int i=0;i<=k[stock]&&i+totlot<=K;i++)
{
lots[stock]=i;
dfs(stock+1,lots,totlot+i);
}
}
int buy_nxt[maxstate][maxn],sell_nxt[maxstate][maxn];
void init()
{
vector<int> lots(n);
states.clear();
ID.clear();
dfs(0,lots,0);
for (int s=0;s//枚举状态
{
int totlot=0;
for (int i=0;i//计算当前状态的总手数
for (int i=0;i1;
if (states[s][i]vector<int> newstate=states[s];
newstate[i]++; //买一股i
buy_nxt[s][i]=ID[newstate];
}
if (states[s][i]>0)
{
vector<int> newstate=states[s];
newstate[i]--;
sell_nxt[s][i]=ID[newstate]; //买一股i
}
}
}
}
double d[maxm][maxstate];
int opt[maxm][maxstate],pre[maxm][maxstate];
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; //这一次的操作
pre[day+1][s2]=s; //上一次的状态
}
}
double dp()
{
for (int day=0;day<=m;day++)
for (int s=0;s//浮点数需要逐个赋值,不能用memset
d[0][0]=c;
for (int day=0;dayfor (int s=0;sdouble v=d[day][s];
if (v<0) continue; //一定要从稳定状态转移
update(day,s,s,v,0); //HOLD
for (int i=0;i//枚举股票
{
if (buy_nxt[s][i]!=-1&&v>=price[i][day]-1e-3)
update(day,s,buy_nxt[s][i],v-price[i][day],i+1); //BUY 为什么非要加1呢,为了避免编号是0的情况
if (sell_nxt[s][i]!=-1)
update(day,s,sell_nxt[s][i],v+price[i][day],-i-1); //SELL
}
}
return d[m][0];
}
void print(int day,int s)
{
if (day==0) return;
print(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 main()
{
int cnt=0;
while (scanf("%lf%d%d%d",&c,&m,&n,&K)!=EOF) //c现金 m天数 n股票种数 K手数上限
{
if (cnt++) printf("\n");
char s[100];
int ss;
for (int i=0;iscanf("%s%d%d",name[i],&ss,&k[i]);
for (int j=0;jdouble pr;
scanf("%lf",&pr);
price[i][j]=(double)pr*ss;
}
}
init();
printf("%.2lf\n",dp());
print(m,0);
}
return 0;
}