[BZOJ1017][JSOI2008][树形DP]魔兽地图DotR

[Problem Description]
DotR (Defense of the Robots) Allstars是一个风靡全球的魔兽地图,他的规则简单与同样流行的地图DotA (Defense of the Ancients) Allstars。DotR里面的英雄只有一个属性——力量。他们需要购买装备来提升自己的力量值,每件装备都可以使佩戴它的英雄的力量值提高固定的点数,所以英雄的力量值等于它购买的所有装备的力量值之和。装备分为基本装备和高级装备两种。基本装备可以直接从商店里面用金币购买,而高级装备需要用基本装备或者较低级的高级装备来合成,合成不需要附加的金币。装备的合成路线可以用一棵树来表示。比如,Sange and Yasha的合成需要Sange, Yasha和Sange and Yasha Recipe Scroll三样物品。其中Sange又要用Ogre Axe, Belt of Giant Strength 和 Sange Recipe Scroll合成。每件基本装备都有数量限制,这限制了你不能无限制地合成某些性价比很高的装备。现在,英雄Spectre有M个金币,他想用这些钱购买装备使自己的力量值尽量高。你能帮帮他吗?他会教你魔法Haunt(幽灵附体)作为回报的。
[Algorithm]
树状dp
[Analysis]
好吧题目有点奇葩上来就告诉你这是个树状dp……但是,如何树状dp啊??蒟蒻想了半天连最裸的办法都没有想出来QAQ 然后看了好几位神犇的BLOG逗比了好长时间才想明白……

首先我们先规定这么几个量:
maxBuy[Node] Node最多能够购买多少
money[Node] 一个Node所花的钱
Need[Node] 制造一个Node的父亲需要的Node的数量
g[Node][j] 表示只有一个Node装备,j元钱可以获得的最大能量(注意这里的意思可以造一个Node装备,也可以替换成造它的原料,或者原料的原料等等……但是最后它们等价于一个Node装备)
f[Node][i][j] 表示Node的父亲造了i个,Node需要无回报制造Need[Node] * i个,有j元钱可以获得的最大的能量

这里我们再引入一个数组间的操作Merge
f[] = a[] Merge b[] :f[k] = max(a[i] + b[k - i]) , 0 <= i <= k
这个Merge操作可以合并两种方案非常好用,并且它满足交换律和结合律!!

开始转移
首先对于每一个点Money = j的决策可以分成两个部分
1.造i个自己
2.f[child][i][j]的和
而f[child][i][j]如何求?由f[child][i][j]可以推出f[child][i - 1][j]
f[child][i - 1][j] = f[child][i][j] Merge g[child] Merge^ Need[child]
这里Merge^p表示自己对自己Merge p次
而f[child][maxBuy[father]][j]如何求呢?
f[child][maxBuy[father]][j] 其实就是child这个装备最多拿maxBuy[child] - maxBuy[father] * Need[child]以及有j元钱的最优解,枚举拿几个装备,再由刚才上面的1和2转移即可……
而g[father][j]可由个g[child][j] Merge 而成。只是最后需要考虑取上自己的情况
好吧我承认我的描述非常渣……还是看代码吧
[Pay Attention]
注意要特别判断满足个数限制但是根据钱数来看一个都造不出来的情况,应该将这种情况下它可以造出来的数量设为0,否则会出现各种re和wa……
由这道题学会了一种dp的奇诡方法:Merge……它满足交换律和结合律
[Code]
/**************************************************************
    Problem: 1017
    User: gaotianyu1350
    Language: C++
    Result: Accepted
    Time:6372 ms
    Memory:2936 kb
****************************************************************/
 
#include <cstdio>
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <iostream>
using namespace std;
 
const int MAXMONEY = 3010;
const int MAXN     = 60;
const int INF      = ((long long)1 << 31) - 1;
 
struct Bag
{
    int f[MAXMONEY], g[MAXMONEY];
    int child[MAXN], need[MAXN], cnt;
    int maxBuy, value, money;
    Bag()
    {
        memset(f, 0, sizeof(f));
        memset(g, 0, sizeof(g));
        cnt = maxBuy = value = money = 0;
    }
} p[MAXN];
int n, m, root;
bool check[MAXN] = {0};
 
inline void MemoryCheck()
{
    printf("%d\n", sizeof(p) / 1024 / 1024);
}
 
inline bool Update(int &a, int b)
{
    if (b > a)
    {
        a = b;
        return true;
    }
    else
        return false;
}
 
inline bool UpdateArray(int *a, int *b)
{
    bool IsUpdate = false;
    for (int i = m; i > 0; i--)
        for (int j = i - 1; j >= 0 ; j--)
            if (Update(a[i], a[j] + b[i - j]))
                IsUpdate = true;
    int maxValue = 0;
    for (int i = 0; i <= m; i++)
    {
        if (a[i] <= maxValue)
            a[i] = 0;
        maxValue = max(maxValue, a[i]);
    }
    return IsUpdate;
}
 
void Dp(int now, int sub)
{
    int temp[MAXMONEY];
    Bag &cur = p[now];
    int *curF = cur.f;
    int *curG = cur.g;
    memset(temp, 0, sizeof(temp));
    for (int i = 1; i <= cur.cnt; i++)
    {
        Dp(cur.child[i], cur.maxBuy * cur.need[i]);
        UpdateArray(temp, p[cur.child[i]].f);
        for (int j = 1; j <= cur.need[i]; j++)
            if (!UpdateArray(curG, p[cur.child[i]].g))
                break;
    }
    bool IsUpdate = true;
    for (int i = cur.maxBuy - sub; i >= 0; i--)
    {
        int tempMoney = i * cur.money;
        int tempValue = i * cur.value;
        for (int j = tempMoney; j <= m; j++)
            Update(curF[j], temp[j - tempMoney] + tempValue);
        if (IsUpdate && !UpdateArray(temp, curG))
            IsUpdate = false;
    }
    if (cur.maxBuy > 0)
        Update(curG[cur.money], cur.value);
}
 
inline void DpInit(int now)
{
    Bag &cur = p[now];
    int minn = INF;
    for (int i = 1; i <= cur.cnt; i++)
    {
        Bag &ch = p[cur.child[i]];
        DpInit(cur.child[i]);
        minn = min(minn, ch.maxBuy / cur.need[i]);
        cur.money += cur.need[i] * ch.money;
    }
    if (!cur.cnt) minn = cur.maxBuy;
    minn = min(minn, m / cur.money);
    cur.maxBuy = minn;
}
 
int main()
{
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++)
    {
        char theType;
        scanf("%d", &p[i].value);
        scanf(" %c", &theType);
        if (theType == 'B')
            scanf("%d%d", &p[i].money, &p[i].maxBuy);
        else
        {
            scanf("%d", &p[i].cnt);
            for (int j = 1; j <= p[i].cnt; j++)
            {
                scanf("%d%d", &p[i].child[j], &p[i].need[j]);
                check[p[i].child[j]] = true;
            }
        }
    }
    for (int i = 1; i <= n; i++)
        if (!check[i])
        {
            root = i;
            break;
        }
    DpInit(root);
    Dp(root, 0);
    int ans = 0;
    for (int i = 0; i <= m; i++)
        ans = max(ans, p[root].f[i]);
    printf("%d\n", ans);
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值