您的位置:首页 > Web前端 > JavaScript

【树形Dp】【JSOI2008】【BZOJ1017魔兽地图DotR】

2013-04-30 21:07 537 查看
【说明】这是VFleaking的题解。参见:http://vfleaking.blog.163.com/blog/static/17480763420130242646240/。但是觉得他有些地方没说清楚吧。

【题解】这是他的代码。

【代码】

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <climits>
using namespace std;

const int MaxN = 51;
const int MaxM = 2000;

struct equipment
{
int energy;

int nNeed;
int needIndex[MaxN];
int needSum[MaxN];

int money;
int maxsum;

char type;

friend inline istream& operator>>(istream &in, equipment &e)
{
in >> e.energy >> e.type;

switch (e.type)
{
case 'A':
in >> e.nNeed;
for (int i = 0; i < e.nNeed; i++)
in >> e.needIndex[i] >> e.needSum[i];
break;

case 'B':
e.nNeed = 0;
in >> e.money >> e.maxsum;
break;
}

return in;
}
};

equipment e[MaxN + 1];
int n, m;

int f[MaxN + 1][MaxM + 1];
int g[MaxN + 1][MaxM + 1];

template <class T>
inline bool tension(T &a, const T &b)
{
if (b < a)
{
a = b;
return true;
}
return false;
}
template <class T>
inline bool relax(T &a, const T &b)
{
if (b > a)
{
a = b;
return true;
}
return false;
}

void getInformation(int idx)
{
equipment *cur = &e[idx];
if (cur->type == 'A')
{
cur->money = 0;
cur->maxsum = INT_MAX;
for (int i = 0; i < cur->nNeed; i++)
{
getInformation(cur->needIndex[i]);

cur->money += e[cur->needIndex[i]].money * cur->needSum[i];
tension(cur->maxsum, e[cur->needIndex[i]].maxsum / cur->needSum[i]);
}
tension(cur->maxsum, m / cur->money);
}
}

inline bool relaxDP(int *a, int *b)
{
bool updated = false;

for (int i = m - 1; i >= 0; i--)
if (a[i] > 0 || i == 0)
for (int j = 1; i + j <= m; j++)
if (b[j] > 0 && relax(a[i + j], a[i] + b[j]))
updated = true;

int maxV = 0;
for (int i = 0; i <= m; i++)
{
if (a[i] <= maxV)
a[i] = 0;
else
maxV = a[i];
}

return updated;
}

void dfs(int idx, int sum)
{
equipment *cur = &e[idx];
for (int i = 0; i < e[idx].nNeed; i++)
dfs(cur->needIndex[i], cur->maxsum * cur->needSum[i]);

int *curF = f[idx], *curG = g[idx];
static int h[MaxM + 1];
fill(h, h + m + 1, 0);
for (int i = 0 ; i < cur->nNeed; i++)
{
relaxDP(h, f[cur->needIndex[i]]);

for (int j = 0; j < cur->needSum[i]; j++)
if (!relaxDP(curG, g[cur->needIndex[i]]))
break;
}

bool updated = true;
for (int i = cur->maxsum - sum; i >= 0; i--)
{
int moneyi = cur->money * i, energyi = cur->energy * i;
for (int j = moneyi; j <= m; j++)
relax(curF[j], h[j - moneyi] + energyi);

if (updated)
updated = relaxDP(h, curG);
}

if (cur->money <= m)
relax(curG[cur->money], cur->energy);
}

inline int handle(int root)
{
getInformation(root);
dfs(root, 0);
return *max_element(f[root], f[root] + m + 1);
}

int main()
{
cin >> n >> m;

for (int i = 1; i <= n; i++)
cin >> e[i];

static bool isRoot[MaxN + 1];
fill(isRoot + 1, isRoot + n + 1, true);
for (int i = 1; i <= n; i++)
if (e[i].type == 'A')
for (int j = 0; j < e[i].nNeed; j++)
isRoot[e[i].needIndex[j]] = false;

int root = find(isRoot + 1, isRoot + n + 1, true) - isRoot;
cout << handle(root) << endl;

return 0;
}


money[u] 表示装备u的单价

maxsum[u]表示可购买装备u的最大数量

needsum[u]表示合成装备u的父亲v,需要装备u的数量

f[u][j]表示以u为根的子树上,正好花j元钱,所能取得的最大力量值,其中对于u本身来说,要满足一个条件限制:装备u的个数不能超过maxsum[u]-maxsum[v]*needsum[u] (v是u的父亲)

对于树根root来说,它没有父亲,因此max{f[root][0] ~ f[root][m]}对应了最终的解。

g[u][j]表示以u为根的子树上,正好花j元钱,所能取得的最大力量值,要满足的条件是:把这棵树上的所有装备全部换算成基本装备,要满足,装备t的个数不超过对于从u到t这个路线上的装备的needsum的乘积。 比如从根到叶分别是u-w-p-t,那么t的个数不超过needsum[w]*needsum[p]*needsum[t]。依次类推。

PS:并不是一定要合成高级装备就能达到更大的力量值,因此g的意义在于,求出到底是以什么样的形态(哪些要合并成高级装备,哪些就保留不进行合成)来表现这些基本装备才能产生最大的力量值。

relaxDp(f,g)
= max{f[i] + g[n-i]} (0<=i<=n),f和g是2个一元函数,这个过程是枚举i,将总值n分成i和n-i两部分,分配给f,g,代表了以合理的分配方式分给2个函数之后,对于总值n能产生的最大效果。

它满足交换律和结合律:

relaxDp(f,g) = relaxDp(g,f)

relaxDp(f,relaxDp(g,h)) = relaxDp(relax(f, g), h)

下面为了方便,多个函数进行relaxDp时候,直接写成relaxDp(f1,f2,f3,f4,f5,...),某个函数进行k次relaxDp时候,写成relaxDp(f^k)

对于一件装备u来说,它的父亲v最多需要maxsum[v]*needsum[u]个u,剩下的部分是与父亲绝对无关的部分,而f就代表了那剩下部分的最大值。求解f的过程如下:

设u的儿子为u1,u2,u3...,首先令g[u][] = relaxDp(g[u1][] ^ needsum[u1] ,g[u2][] ^ needsum[u2], g[u3][] ^ needsum[u3],...),那么g[u][]就代表了以u为根的子树,正好花j元钱,能产生的最大值,其中满足条件是:把这棵树上的所有装备全部换算成基本装备,要满足,装备t的个数不超过对于从u到t这个路线上的装备的needsum的乘积(同上g的定义)。但是有1种情况需要排除,就是在钱够的基础下,能合成就合成,最终变成1个u,这种情况需要排除(这是显然的,因为根节点是需要我们去枚举的)。

枚举究竟最终合成了几个装备u,也就是代码中的这个部分:

for (int i = cur->maxsum - sum; i >= 0; i--)
{
int moneyi = cur->money * i, energyi = cur->energy * i;
for (int j = moneyi; j <= m; j++)
relax(curF[j], h[j - moneyi] + energyi);

if (updated)
updated = relaxDP(h, curG);
}


4~5行表示,能合成就合成,最终变成了1件装备u,7~8行表示,不需要能合成就合成,在2者中取最大值,得到f。

PS:关于relaxDp的过程,有段代码是:

int maxV = 0;
for (int i = 0; i <= m; i++)
{
if (a[i] <= maxV)
a[i] = 0;
else
maxV = a[i];
}


它的意思是,花了更多钱却得到少的力量值,这种状态属于无效状态(VFleaking一句话就让我明白了这个地方的优化,确实很给力,不加这段2000+MS,加了这段300+MS)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: