UVa1412 - Fund Management(状压dp【复杂)

题目链接

简介:
你有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

注意price[i][day]表示第day天第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)         //钱必须够才能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]);
}

tip

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;
}

你可能感兴趣的:(UVa/LA,dp)