//392K 16MS C++
#include <cstdio>
#include <cstring>
using namespace std;
const int MAX = 6005;
struct Employee {
int supervisorId;
int val;
int nextBroId;
};
typedef struct Employee Employee;
Employee employees[MAX];
int childListHead[MAX];
int employeeNum;
int DP[MAX][2]; // 0: not invite, 1: invite
void insert(int employeeId, int supervisorId) {
employees[employeeId].nextBroId = childListHead[supervisorId];
childListHead[supervisorId] = employeeId;
employees[employeeId].supervisorId = supervisorId;
}
int max(int A, int B) {
return A > B ? A: B;
}
int dfs(int curNodeId) {
// leaf Node
if (childListHead[curNodeId] == 0) {
DP[curNodeId][0] = 0;
DP[curNodeId][1] = employees[curNodeId].val;
return 0;
}
int childNodeId = childListHead[curNodeId];
while(childNodeId) {
dfs(childNodeId);
childNodeId = employees[childNodeId].nextBroId;
}
for (childNodeId = childListHead[curNodeId]; childNodeId;
childNodeId = employees[childNodeId].nextBroId) {
// supervsior come, child can not come
DP[curNodeId][1] += DP[childNodeId][0];
// supervsior do not come, child can come
DP[curNodeId][0] += max(DP[childNodeId][1], DP[childNodeId][0]);
}
// for the case supervsior is invited, +1
DP[curNodeId][1] += employees[curNodeId].val;
return 0;
}
int main() {
while(scanf("%d", &employeeNum) != EOF) {
memset(childListHead, 0, sizeof(childListHead));
memset(employees, 0, sizeof(employees));
memset(DP, 0, sizeof(DP));
if (employeeNum == 0) {
int tmp;
scanf("%d", &tmp);
return 0;
}
for (int i = 1; i <= employeeNum; i++) {
scanf("%d", &(employees[i].val));
}
for (int i = 1; i < employeeNum; i++) {
int employeeId;
int supervisorId;
scanf("%d %d", &employeeId, &supervisorId);
insert(employeeId, supervisorId);
}
int topSupervisorId = 0;
for (int i = 1; i <= employeeNum; i++) {
// find the node who has no parents
if (employees[i].supervisorId == 0) {
topSupervisorId = i;
break;
}
}
dfs(topSupervisorId);
printf("%d\n", DP[topSupervisorId][1] > DP[topSupervisorId][0] ?
DP[topSupervisorId][1] : DP[topSupervisorId][0]);
}
}
树状DP的入门题,这道题让我稍微对与树状DP有些概念了,其实和普通DP的本质区别在于,
普通DP的方向是一个一维的方向(在code里经常表示为DP数组每一个维度的变量都只能单向变化,及一直递增或者以及递减),这也是因为普通DP的状态间依赖关系决定的,比如 DP[N]的结果就依赖于DP[1..N-1],那么DP数组的这个维度变化就是单向递增的。
而树状DP则不一样,因为其结果是树,因此,一般来说,依赖关系应该有两种:子节点依赖于父节点,或者父节点依赖于字节点,如果给一个树的节点也都1到N编号的话,根据树的形状的不同,DP[X]中的X既可能依赖于DP[1..X-1]也可能依赖于DP[X+1...N](没有了一定的单调方向,但是还是有方向的,即从父到子和从子到父), 因此,这时候,直接用普通DP的那种循环递归的单向求DP数组的每个值,一定是不行的。
这时候,既然目的是按照DP状态的依赖关系来遍历DP数组,那么对于树来说,也只需要想办法能按照依赖关系访问树的每个节点即可,这时候,就是dfs登场的时候,
在某一层dfs某个节点X的DP时,如果X节点所依赖的子节点DP没有求出来(假设这次是父节点依赖于子节点),那么必然要先遍历X的所有子节点,依次递归DFS每个子节点,将子节点的全部处理完以后再处理当前父节点。这就是完全是一个dfs遍历树的过程。
一般这种题,都伴随这树的建立,本题因为是入门题,所以隐含的告诉了该题的树是有根树,而根就是那个职位最高的人,dfsDP就是从这个人那里开始,
题目对树的描述是给出了各种上下级关系, 这种情况,可以搞两个数组来表示这棵树, 一个是表示树各个节点的nodes[N] 一个是表示树的每个节点的孩子节点索引列表的头childListHead[N],
nodes本身保存其父节点的id(就是题目事先编好的节点id号),和其一下一个兄弟节点的id号,以及一个val来保存其活跃度,每次有一个上下级关系输入,比如A 是 B的上次, 那么 首先 nodes[B].parent = A, 然后 nodes[B].nextBro = childListHead[A](之前A的chilid列表的第一个孩子的id), 并且 childListHead[A] = B(其实就是将B插入到了A的chiildList的首位)。
在全部的输入关系处理完以后,就可以遍历所有的node来找出rootnode了,很简单,只有一个node没有parent,只要找到一个parent = 0(nodeId从1开始,0代表没有),那么就从该node开始dfsDP,
dfs的逻辑:
首先,判断此node i是否是叶子节点(已经没有子节点需要进一步的dfs), 如果是叶子,那么直接
DP[i][0] = 0; (DP[i][0] 代表不邀请第i个人, 其下属能达到的最大活跃值)
DP[i][1] = V[i] (DP[i][1] 代表邀请第 i 个人,其下属能达到的最大活跃值, V[i] 表示 i 的活跃度)。
如果不是叶子节点,首先要遍历i的所有子节点 j,dfs求出其DP[j][0]/DP[j][1]
在求完子节点以后,重现遍历所有子节点,
对于节点j, 如果邀请了i,那么由题意, j就不可能被邀请, 那么 DP[i][1] += DP[j][0](注意是+=, 因为i会有多个下属,每个下属的活跃度都要加)
如果不邀请i, 那么j可以被邀请,也可以不邀请,找出其中最大值,DP[i][0] += max(DP[j][1], DP[j][0])
最后,遍历完了以后,还要注意 DP[i][1] += V[i](因为上边的都只考虑了下属, 最后要把i自己的活跃度加进去)。
最后,输出DP[root][1] DP[root][0]的最大者.
本文深入浅出地介绍了树状动态规划的概念及其应用,通过实例演示如何利用树状DP解决具有多层决策的问题。重点阐述了与传统DP的区别,以及在树形结构中进行状态转移的方法,特别强调了使用深度优先搜索(DFS)遍历树以求解最优解的策略。此外,文章还提供了代码实现细节,帮助读者理解如何将理论转化为实际编程解决方案。

485

被折叠的 条评论
为什么被折叠?



