【树形Dp】【JSOI2008】【BZOJ1017魔兽地图DotR】
【说明】这是VFleaking的题解。参见:http://vfleaking.blog.163.com/blog/static/17480763420130242646240/。但是觉得他有些地方没说清楚吧。
【题解】这是他的代码。
【代码】
1 #include <iostream> 2 #include <cstdio> 3 #include <algorithm> 4 #include <climits> 5 using namespace std; 6 7 const int MaxN = 51; 8 const int MaxM = 2000; 9 10 struct equipment 11 { 12 int energy; 13 14 int nNeed; 15 int needIndex[MaxN]; 16 int needSum[MaxN]; 17 18 int money; 19 int maxsum; 20 21 char type; 22 23 friend inline istream& operator>>(istream &in, equipment &e) 24 { 25 in >> e.energy >> e.type; 26 27 switch (e.type) 28 { 29 case 'A': 30 in >> e.nNeed; 31 for (int i = 0; i < e.nNeed; i++) 32 in >> e.needIndex[i] >> e.needSum[i]; 33 break; 34 35 case 'B': 36 e.nNeed = 0; 37 in >> e.money >> e.maxsum; 38 break; 39 } 40 41 return in; 42 } 43 }; 44 45 equipment e[MaxN + 1]; 46 int n, m; 47 48 int f[MaxN + 1][MaxM + 1]; 49 int g[MaxN + 1][MaxM + 1]; 50 51 template <class T> 52 inline bool tension(T &a, const T &b) 53 { 54 if (b < a) 55 { 56 a = b; 57 return true; 58 } 59 return false; 60 } 61 template <class T> 62 inline bool relax(T &a, const T &b) 63 { 64 if (b > a) 65 { 66 a = b; 67 return true; 68 } 69 return false; 70 } 71 72 void getInformation(int idx) 73 { 74 equipment *cur = &e[idx]; 75 if (cur->type == 'A') 76 { 77 cur->money = 0; 78 cur->maxsum = INT_MAX; 79 for (int i = 0; i < cur->nNeed; i++) 80 { 81 getInformation(cur->needIndex[i]); 82 83 cur->money += e[cur->needIndex[i]].money * cur->needSum[i]; 84 tension(cur->maxsum, e[cur->needIndex[i]].maxsum / cur->needSum[i]); 85 } 86 tension(cur->maxsum, m / cur->money); 87 } 88 } 89 90 inline bool relaxDP(int *a, int *b) 91 { 92 bool updated = false; 93 94 for (int i = m - 1; i >= 0; i--) 95 if (a[i] > 0 || i == 0) 96 for (int j = 1; i + j <= m; j++) 97 if (b[j] > 0 && relax(a[i + j], a[i] + b[j])) 98 updated = true; 99 100 int maxV = 0; 101 for (int i = 0; i <= m; i++) 102 { 103 if (a[i] <= maxV) 104 a[i] = 0; 105 else 106 maxV = a[i]; 107 } 108 109 return updated; 110 } 111 112 void dfs(int idx, int sum) 113 { 114 equipment *cur = &e[idx]; 115 for (int i = 0; i < e[idx].nNeed; i++) 116 dfs(cur->needIndex[i], cur->maxsum * cur->needSum[i]); 117 118 int *curF = f[idx], *curG = g[idx]; 119 static int h[MaxM + 1]; 120 fill(h, h + m + 1, 0); 121 for (int i = 0 ; i < cur->nNeed; i++) 122 { 123 relaxDP(h, f[cur->needIndex[i]]); 124 125 for (int j = 0; j < cur->needSum[i]; j++) 126 if (!relaxDP(curG, g[cur->needIndex[i]])) 127 break; 128 } 129 130 bool updated = true; 131 for (int i = cur->maxsum - sum; i >= 0; i--) 132 { 133 int moneyi = cur->money * i, energyi = cur->energy * i; 134 for (int j = moneyi; j <= m; j++) 135 relax(curF[j], h[j - moneyi] + energyi); 136 137 if (updated) 138 updated = relaxDP(h, curG); 139 } 140 141 if (cur->money <= m) 142 relax(curG[cur->money], cur->energy); 143 } 144 145 inline int handle(int root) 146 { 147 getInformation(root); 148 dfs(root, 0); 149 return *max_element(f[root], f[root] + m + 1); 150 } 151 152 int main() 153 { 154 cin >> n >> m; 155 156 for (int i = 1; i <= n; i++) 157 cin >> e[i]; 158 159 static bool isRoot[MaxN + 1]; 160 fill(isRoot + 1, isRoot + n + 1, true); 161 for (int i = 1; i <= n; i++) 162 if (e[i].type == 'A') 163 for (int j = 0; j < e[i].nNeed; j++) 164 isRoot[e[i].needIndex[j]] = false; 165 166 int root = find(isRoot + 1, isRoot + n + 1, true) - isRoot; 167 cout << handle(root) << endl; 168 169 return 0; 170 }
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)[n] = 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,也就是代码中的这个部分:
1 for (int i = cur->maxsum - sum; i >= 0; i--) 2 { 3 int moneyi = cur->money * i, energyi = cur->energy * i; 4 for (int j = moneyi; j <= m; j++) 5 relax(curF[j], h[j - moneyi] + energyi); 6 7 if (updated) 8 updated = relaxDP(h, curG); 9 }
4~5行表示,能合成就合成,最终变成了1件装备u,7~8行表示,不需要能合成就合成,在2者中取最大值,得到f。
PS:关于relaxDp的过程,有段代码是:
1 int maxV = 0; 2 for (int i = 0; i <= m; i++) 3 { 4 if (a[i] <= maxV) 5 a[i] = 0; 6 else 7 maxV = a[i]; 8 }
它的意思是,花了更多钱却得到少的力量值,这种状态属于无效状态(VFleaking一句话就让我明白了这个地方的优化,确实很给力,不加这段2000+MS,加了这段300+MS)