没有上司的舞会

GitHub跳转原题关系图返回列表

用树形 DP 维护每个员工选与不选两种状态,父子不能同时选择。

OJ: luogu

题目 ID: P1352

难度:普及/提高-

标签:树形DP动态规划

日期: 2026-06-22 22:59

题意

给定一棵上下级关系树,每个员工有快乐值。

不能同时邀请一个员工和他的直接上司,求最大快乐值。

思路

先看一个可以直接验证想法的朴素解:

cpp
#include <bits/stdc++.h>
using namespace std;

// brute.cpp:小数据枚举每个员工是否参加,检查上下级冲突。

const int MAXN = 25;

int n;
int happy[MAXN];
int child_node[MAXN], parent_node[MAXN];
int choose_flag[MAXN];
int answer;

void dfs_enum(int pos) {
    if (pos == n + 1) {
        for (int i = 1; i < n; i++) {
            if (choose_flag[child_node[i]] && choose_flag[parent_node[i]]) {
                return;
            }
        }

        int sum = 0;
        for (int i = 1; i <= n; i++) {
            if (choose_flag[i]) {
                sum += happy[i];
            }
        }
        answer = max(answer, sum);
        return;
    }

    choose_flag[pos] = 0;
    dfs_enum(pos + 1);
    choose_flag[pos] = 1;
    dfs_enum(pos + 1);
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> happy[i];
    }
    for (int i = 1; i < n; i++) {
        cin >> child_node[i] >> parent_node[i];
    }

    answer = 0;
    dfs_enum(1);
    cout << answer << '\n';

    return 0;
}

暴力枚举每个人参加或不参加会有 2^n 种方案。树上父子限制适合用树形 DP。

定义:

text
dp[u][0] = 不邀请 u 时,u 子树内最大快乐值
dp[u][1] = 邀请 u 时,u 子树内最大快乐值

若不邀请 u,每个孩子可以来,也可以不来:

text
dp[u][0] += max(dp[v][0], dp[v][1])

若邀请 u,所有直接孩子都不能来:

text
dp[u][1] += dp[v][0]

最后答案是:

text
max(dp[root][0], dp[root][1])

代码

cpp
#include <bits/stdc++.h>
using namespace std;

const int MAXN = 6005;

int n;
int happy[MAXN];
vector<int> children[MAXN];
bool has_parent[MAXN];
int dp[MAXN][2]; // dp[u][0]:不选 u 的最大快乐值;dp[u][1]:选择 u 的最大快乐值。

void read_input() {
    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> happy[i];
    }

    for (int i = 1; i < n; i++) {
        int child, parent;
        cin >> child >> parent;
        children[parent].push_back(child);
        has_parent[child] = true;
    }
}

void dfs(int u) {
    dp[u][0] = 0;
    dp[u][1] = happy[u];

    for (int i = 0; i < (int)children[u].size(); i++) {
        int v = children[u][i];
        dfs(v);

        // 不选 u 时,孩子 v 可选可不选,取更优。
        dp[u][0] += max(dp[v][0], dp[v][1]);

        // 选择 u 时,直接下属 v 不能参加。
        dp[u][1] += dp[v][0];
    }
}

void solve() {
    int root = 1;
    for (int i = 1; i <= n; i++) {
        if (!has_parent[i]) {
            root = i;
            break;
        }
    }

    dfs(root);
    cout << max(dp[root][0], dp[root][1]) << '\n';
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    read_input();
    solve();

    return 0;
}

复杂度

每个点处理一次,时间复杂度为 O(n)O(n),空间复杂度为 O(n)O(n)

总结

树形 DP 的第一步是想清楚“父节点如何影响子节点”。

本题中影响只有一种:父亲来了,孩子不能来,所以每个点用“选/不选”两个状态即可。