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]