poj-2342

22 篇文章 0 订阅
//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]的最大者.

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值