UVA 1412 Fund Management (复杂状态的DP,状态池)
受到前面题目的影响,一开始的思路是用d(i,s)表示经过第i天后,选择了集合s里面的股票的当前最大现金。但是发股票可以选多手,根本无法用集合表示。
由于每只股票的可持有的手数为(0<=ki<=8),最多有8只股票,所以表示当前手持股票资产的理论状态数最多有98<5*107种,但是由于还有一个持有总手数k的最大限制,实际状态数将远远低于这个值。实际状态数<1.5e4(我也不知道为什么)
所以用d(i,p)表示经过i天后,当前手持股票资产为p时的最大现金数。因为每只股票可持有的手数为0~8,所以可以用一个9进制的数去表示一个状态p。但是在状态转移的过程中,需要对整数p进行解码、编码的操作十分费时,对于这道题会导致TLE。
借鉴紫书上的做法,先将所有的状态数枚举出来并进行标号储存,这样我们就可以建立起一个状态–自然数的映射。并将每个状态可转移到的状态也通过预处理枚举并储存,这样就大大减少了dp过程的时间。
同时因为股票只能一手一手的买,我们可以将一手的价格预处理出来,这样可以避免重复的运算。
这道理的状态转移过程是十分明了的,可以从题目一眼看是,对于第i天,可以保持(HOLD),可以选择一只股票进行买一手(BUY)或者卖一手(SELL)。
由于是否可以买股票要取决于当前的现金是否足够,所以需要判断。所以这道题并不是DAG上的动态规划,因为edge(u,v)是否存在取决于之前的决策。所以不可以像DAG那样逆着去定义状态:如果用d(i,p)去表示从第i天开始到最后能拥有的最大现金值,那么状态将无法转移,因为不能够判断对于第i-1天,是否有足够的钱去买某个股票的一手。
具体实现上:
用一个vector去表示一个状态,那么可以用一个大vector来作状态池来储存状态,并将每个状态对应的下标作为标号用map建立起映射(ID)。
用buy_next[p][i]来表示在p状态购买了i股票后转移到的状态的编号(合法的),同理用sell_next[p][i]来表示在p状态卖出了i股票后转移到的状态的编号(合法的)。
因为需要打印答案,用pre[i[[p]表示在d(i,p)前一个状态的标号,用opt[i][p]表示在d(i,p)的操作:
当opt[i][p]==0时,表示HOLD操作
当opt[i][p]>0时,表示BUY编号为opt[i][p]-1的股票。
当opt[i][p]<0时,表示SELL编号为-(opt[i][p]+1)的股票。
在打印时只需递归进行打印就好了。
代码如下:
#include<bits/stdc++.h>
using namespace std;
const int max_state=15000;
const int INF=-0x3f3f3f3f;
double c;
char name[10][30];
int si[10];
int ki[10];
double ci[10][105];
vector<vector<int> > state;
map<vector<int>,int> ID;
int buy_next[max_state][10];
int sell_next[max_state][10];
double dp[105][max_state];
int pre[105][max_state];
int opt[105][max_state];
int n,m,k;
void dfs(vector<int> &lot,int s,int total_k)
{
if(s==n){
ID[lot]=state.size();
state.push_back(lot);
return ;
}
for(int i=0;i<=ki[s]&&total_k+i<=k;i++)
{
lot[s]=i;
dfs(lot,s+1,total_k+i);
}
}
void init(void)
{
for(int s=0;s<state.size();s++)
for(int i=0;i<n;i++)
{
int total_k=0;
for(int j=0;j<state[s].size();j++)
total_k+=state[s][j];
buy_next[s][i]=sell_next[s][i]=-1;
if(state[s][i]>0){
vector<int> newstate=state[s];
newstate[i]--;
sell_next[s][i]=ID[newstate];
}
if(state[s][i]<ki[i]&&total_k<k){
vector<int> newstate=state[s];
newstate[i]++;
buy_next[s][i]=ID[newstate];
}
}
}
void update(int day,int s,int s2,double v,int option)
{
if(v>dp[day+1][s2]){
dp[day+1][s2]=v;
pre[day+1][s2]=s;
opt[day+1][s2]=option;
}
}
void update_ans(int day,int s)
{
if(day==0)return ;
update_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)]);
}
void solve(void)
{
state.clear();
ID.clear();
vector<int> lot(n);
dfs(lot,0,0);
init();
for(int i=0;i<=m;i++)
for(int s=0;s<state.size();s++)
dp[i][s]=INF;
dp[0][0]=c;
for(int i=0;i<m;i++)
for(int s=0;s<state.size();s++)
{
double v=dp[i][s];
if(v<0)continue;
update(i,s,s,v,0);
for(int j=0;j<n;j++)
{
if(buy_next[s][j]!=-1&&v>=ci[j][i+1])
// if(buy_next[s][j]!=-1&&v>=ci[j][i+1]-1e-3)
update(i,s,buy_next[s][j],v-ci[j][i+1],j+1);
if(sell_next[s][j]!=-1)
update(i,s,sell_next[s][j],v+ci[j][i+1],-j-1);
}
}
printf("%.2f\n",dp[m][0]);
update_ans(m,0);
printf("\n");
}
int main(void)
{
while(~scanf("%lf%d%d%d",&c,&m,&n,&k))
{
for(int i=0;i<n;i++)
{
scanf("%s%d%d",name[i],&si[i],&ki[i]);
for(int j=1;j<=m;j++)
{
double temp;
scanf("%lf",&temp);
ci[i][j]=temp*(double)si[i];
}
}
solve();
}
}
1717



被折叠的 条评论
为什么被折叠?



