• 洛谷P1352 没有上司的舞会 题解 树形DP


    题目链接:https://www.luogu.com.cn/problem/P1352

    解题思路:树形DP。

    首先,我们可以把单位的层级结构抽象成一个树的问题:
    每个人都是树上的一个节点,某一个人的直属领导对应该节点的父节点;某一个人的所有直属下属对应该节点的所有子节点。

    现在假设我当前所处的点是点 (u),那么我面临两种选择:

    • (u) 参加聚会;
    • 不让 (u) 参加聚会。

    所以我可以开一个二维数组 (f[n][2]),其中:

    • (f[u][0]) 表示 不选择 节点 (u) 时以点 (u) 为根节点的子树的最大欢乐值;
    • (f[u][1]) 表示 选择 节点 (u) 时以点 (u) 为根节点的子树的最大欢乐值。

    为了方便接下来做统计,我们再定义一些变量:

    • (h[i]) 表示第 (i) 个人的欢乐值;

    我们可以发现,对于点 (u)

    当我不选 (u) 的时候,它的所有儿子节点可以选,也可以不选,此时

    [f[u][0] = sum_{v} max( f[v][0], f[v][1] ) ]

    其中,(v) 代表 (u) 的所有子节点。

    当我选择 (u),它的所有儿子节点都不能选,此时

    [f[u][1] = h[u] + sum_{v} f[v][0] ]

    其中,(v) 代表 (u) 的所有子节点。

    而上述两个公式合到一起就是我们的总的状态转移方程。

    我们发现,要求一个节点 (u) 对应的 (f[u][0])(f[u][1]) ,得先求出它的所有儿子节点的 (f) 值,而叶子节点的 (f) 值可以直接得出(边界条件,对于叶子节点 (u) 来说,(f[u][0]=0,f[u][1]=h[u])),这恰好是一种递归结构,可以用记忆化搜索来实现(事实上,对于树形结构的问题,如树形DP,一般使用记忆化搜索的方式编写代码会比较方便)。

    所以我们可以使用记忆化搜索的方式来编写如下代码段:

    void dfs(int u, int fa) {   // u是当前节点,fa用于指代u的父节点
        f[u][0] = 0;        // 不选择u的情况下初始为0,不包含h[u]
        f[u][1] = h[u];     // 选择u的情况下必然包含h[u],所以一开始就加上
        int sz = g[u].size();   // 获得u的儿子的个数
        for (int i = 0; i < sz; i ++) { // 遍历u的所有邻接点(其中sz-1个都是u的儿子)
            int v = g[u][i];
            if (v == fa) continue;  // 和u相连的有一个是u的父节点,遇到的时候跳过
            dfs(v, u);              // 先递归遍历子节点计算出f[v][0]和f[v][1]
            f[u][0] += max(f[v][0], f[v][1]);   // 再奖答案汇总
            f[u][1] += f[v][0];
        }
    }
    

    上述代码实现了

    [f[u][0] = sum_{v} max( f[v][0], f[v][1] ) ]

    [f[u][1] = h[u] + sum_{v} f[v][0] ]

    并且我们可以发现,上述公式对于叶子节点也成立,因为当 (u) 为叶子节点时:

    [f[u][0] = sum_{v} max( f[v][0], f[v][1] ) = 0 ]

    [f[u][1] = h[u] + sum_{v} f[v][0] = h[u] ]

    完整实现代码如下:

    #include <bits/stdc++.h>
    using namespace std;
    const int maxn = 6060;
    int f[maxn][2], h[maxn], p[maxn], n;
    vector<int> g[maxn];
    void dfs(int u, int fa) {   // u是当前节点,fa用于指代u的父节点
        f[u][0] = 0;
        f[u][1] = h[u];
        int sz = g[u].size();
        for (int i = 0; i < sz; i ++) {
            int v = g[u][i];
            if (v == fa) continue;
            dfs(v, u);
            f[u][0] += max(f[v][0], f[v][1]);
            f[u][1] += f[v][0];
        }
    }
    int main() {
        cin >> n;
        for (int i = 1; i <= n; i ++) cin >> h[i];
        for (int i = 1; i < n; i ++) {
            int a, b;
            cin >> a >> b;
            p[a] = b;
            g[b].push_back(a);
        }
        int rt;
        for (int i = 1; i <= n; i ++) {
            if (!p[i]) {
                rt = i;
                break;
            }
        }
        dfs(rt, -1);
        cout << max(f[rt][0], f[rt][1]) << endl;
        return 0;
    }
    

    注意:我们主函数里的rt变量是为了求得该树的根节点,因为只有根节点是没有直属领导的,所以只有 (p[rt]= 0)
    然后找到 rt 之后就可以执行 (dfs(rt, -1)) ,这将会求得所有点的 (f) 值(第二个参数 (-1) 是用来表示 (rt) 的父节点,因为 (rt) 不存在父节点,所以我们开一个不在 (1 sim n) 范围内的数就可以了,我这里出于习惯用了一个 (-1))。

  • 相关阅读:
    declare set声明注意
    Winform 的dadagridview控件的修改操作
    VS2010,VS2008,VS2005;工程之间的转换
    C#程序跨平台?
    上网黑色护眼,设置浏览器黑色风格
    AutoCompleteSource从文件里读取自动填充内容
    两个checkbox的控件控制操作只能选其一
    《博客园精华集CLR/C#分册》第三轮筛选结果 转载
    TransactSQL 示例 查询某个数据库内的所有表的记录行数及其总和
    EF 4.1中内部经常提交的 exec sp_reset_connection 的用途原来是为了重用池中的连接
  • 原文地址:https://www.cnblogs.com/quanjun/p/12236208.html
Copyright © 2020-2023  润新知