F树形 DP

选点 / 最大独立集

没有上司的舞会

本课摘要

选点 / 最大独立集课程回答“父子不能同时选择时,选与不选状态如何配合”。内容以没有上司的舞会为主线,配合逐步推导、可编辑演示、例题与练习形成可复查的学习闭环。

  • 判断选点 / 最大独立集的适用条件与状态边界
  • 围绕“没有上司的舞会”推导转移与计算顺序
  • 用演示、复杂度分析和配套题目校验实现
本课目录 · 0 节

正在整理目录…

从一场不能同席的舞会说起

公司是一棵树:董事长在根,每个人往下带若干直接下属。现在办舞会,每个人来了会带来一份欢乐值。 只有一条规矩——任何人都不愿与自己的直接上司同场。要让到场的总欢乐值最大,该请谁?

用图论的话说:在树上选一个点集,使得没有任何一条边的两端同时被选(这样的点集叫「独立集」), 并让选中点的权和最大。这就是最大权独立集。

先想想能不能贪心:按欢乐值从大到小挑,能选就选?会翻车。设董事长欢乐值 1010,他有两个下属各 66, 两个下属又各带一个孙辈 66。贪心先抢董事长(10),于是两个下属都不能选;孙辈可选,得 10+6+6=2210+6+6=22。 但只要放弃董事长、改选两个下属加两个孙辈,就是 6×4=246\times4=24——贪心又输了。

那枚举每个点「选 / 不选」的所有组合呢?2n2^n 种,n=6000n=6000 直接爆炸。 问题的结构是树,而树天生适合把子树的答案往上合并——这正是树形 DP 的舞台。

1523344152金色序号 = 处理次序:叶子 4、5 先算好,父亲 2 才能合并;根 1 最后收口。
树形 DP 的处理次序是后序遍历:先把每棵子树算透,父亲才拿孩子的结果做决策。

状态与转移:这个点,选还是不选

定状态。对每个点 uu 开两个状态,把「u 自己选没选」记进状态里:

f[u][0]: u ;f[u][1]: uf[u][0]:\ u\ \text{;}\quad f[u][1]:\ u

读作:f[u][0]f[u][0] = u 不选时,以 u 为根的整棵子树能取到的最大权;f[u][1]f[u][1] = u 选时子树的最大权。 把状态开成两份,是为了让父亲知道「孩子到底选没选」——因为父子不能同时选,这个信息必须显式带上。

节点 u(权 w)选它?不选 u选 u孩子自由:各取较大dp[u][0] =Σ max(dp[c][0], dp[c][1])孩子必须全不选dp[u][1] = w +Σ dp[c][0]答案 = max(dp[root][0], dp[root][1])
u 不选,孩子自由(各取 max⁡\max);u 选,孩子被禁(只能取孩子的「不选」态)。

u 不选:它没占位,每个孩子 cc 选不选都行,各自取更优的那个:

f[u][0]=∑c∈son(u)max⁡(f[c][0], f[c][1])f[u][0]=\sum_{c\in son(u)}\max\big(f[c][0],\,f[c][1]\big)

u 选:它占了位,所有孩子都不许选,只能取孩子的「不选」态,再加上 u 自己的权 wuw_u:

f[u][1]=wu+∑c∈son(u)f[c][0]f[u][1]=w_u+\sum_{c\in son(u)}f[c][0]

边界:叶子没有孩子,f[leaf][0]=0f[\text{leaf}][0]=0、f[leaf][1]=wleaff[\text{leaf}][1]=w_{\text{leaf}}。 答案在根:max⁡(f[root][0], f[root][1])\max(f[root][0],\,f[root][1])。

本质

把「u 选没选」压进状态,父子那条唯一的约束就变成了两条干净的求和公式;2n2^n 的组合塌缩成每个点 O(1)O(1) 的合并,总复杂度 O(n)O(n)。这套 f[u][0/1]f[u][0/1] 是所有树形 DP 的第一块积木。

跟着算一遍

用一棵小树:根 11(权 3)带两个孩子 22(权 6)、33(权 2);22 再带两个叶子 44(权 4)、55(权 7)。后序次序 4,5,2,3,14,5,2,3,1:

1
叶子 4、5、3。 没有孩子:f[4]=(0,4)f[4]=(0,4)、f[5]=(0,7)f[5]=(0,7)、f[3]=(0,2)f[3]=(0,2)(左不选、右选)。
2
节点 2(权 6,孩子 4、5)。不选 2:max⁡(0,4)+max⁡(0,7)=4+7=11\max(0,4)+\max(0,7)=4+7=11。选 2:6+f[4][0]+f[5][0]=6+0+0=66+f[4][0]+f[5][0]=6+0+0=6。得 f[2]=(11,6)f[2]=(11,6)。
3
根 1(权 3,孩子 2、3)。不选 1:max⁡(11,6)+max⁡(0,2)=11+2=13\max(11,6)+\max(0,2)=11+2=13。选 1:3+f[2][0]+f[3][0]=3+11+0=143+f[2][0]+f[3][0]=3+11+0=14。得 f[1]=(13,14)f[1]=(13,14)。
✓
答案 max⁡(13,14)=14\max(13,14)=14。最优取法:选 1、4、5(欢乐 3+4+7=14),没有任何一对直接上下级同时到场。
下面的演示把这棵树的后序过程逐点点亮:改任意员工的欢乐值,看 f[u][0/1]f[u][0/1] 自底向上重新填入,根节点吐出答案。

看 dp 自底向上长出来

改每个员工的欢乐值,看 dp 自底向上重算
1
董事长 · 欢乐值
3
2
经理A · 欢乐值
6
3
经理B · 欢乐值
2
4
主管 · 欢乐值
5
5
员工X · 欢乐值
4
6
员工Y · 欢乐值
7
后序遍历(孩子先于父亲)逐个点亮节点,节点下方两行是 dp[u][0](不选 u)/ dp[u][1](选 u)。 走到根,答案 = max(dp[1][0], dp[1][1]) = 18。
1w=32w=63w=24w=550:01:46w=7
已暂停,第 1 步,共 6 步,1 倍速
当前处理 dp 已确定 入选最优独立集
叶子 5:不选为 0,选择为点权 4。

翻个面:最小点覆盖,转移方向正好相反

换一个问题:树上放最少的士兵看守所有边——每条边至少有一端站着士兵(这叫最小点覆盖)。 状态还是 f[u][0/1]f[u][0/1](u 不放 / 放),但转移和独立集恰好对称:

f[u][0]=∑cf[c][1]f[u][1]=1+∑cmin⁡(f[c][0],f[c][1])f[u][0]=\sum_{c}f[c][1]\qquad f[u][1]=1+\sum_{c}\min\big(f[c][0],f[c][1]\big)

盯住 f[u][0]f[u][0]:u 不放兵,那每条 u-cu\text{-}c 的边就只能靠 c 那端守,于是孩子必须放(取 f[c][1]f[c][1])。 这与独立集「u 不选、孩子自由取 max⁡\max」正好反过来——独立集要「避免相邻」,点覆盖要「盯住每条边」。

最大独立集:选 {2,4}1234最小点覆盖:选 {1,3}1234选中集互为补集:独立集要「谁都不挨着」,点覆盖要「每条边至少一端被选」。
同一棵树:最大独立集与最小点覆盖的选中集互为补集(König 定理在树上的直观体现)。

常见陷阱:别把「孩子自由」照抄过来

写点覆盖时最容易犯的错,是把独立集的 f[u][0]=∑max⁡(… )f[u][0]=\sum\max(\dots) 直接搬来。u 不放兵,孩子就没有「自由」——边必须有人守,孩子被强制取 f[c][1]f[c][1]。看清「约束落在点上还是边上」,转移方向就不会写反。更复杂的「支配集」还要引入第三个状态,见 覆盖 / 支配 / 染色。

例题

P1352没有上司的舞会洛谷原生普及/提高-
题意
nn 名职员构成一棵树,每人有快乐值 rir_i。若某人来了,他的直接上司就不来。求到场者快乐值之和的最大值。
对应关系
标准最大权独立集。f[u][0]f[u][0] = u 不来时子树最大快乐,f[u][1]f[u][1] = u 来时子树最大快乐;答案取根的两态较大。
转移 · 复杂度
f[u][0]=∑max⁡(f[c][0],f[c][1])f[u][0]=\sum\max(f[c][0],f[c][1]),f[u][1]=ru+∑f[c][0]f[u][1]=r_u+\sum f[c][0];一遍 DFS,O(n)O(n)。
参考代码(邻接表 + 后序 DFS)
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

const int N = 6005;
vector<int> g[N];             // 邻接表:g[u] 存 u 的直接下属
int r[N];                     // 每个人的欢乐值
int f[N][2];                  // f[u][0/1]:u 不选/选时,u 子树的最大欢乐值
int fa[N];                    // 记录父亲,用来找根
bool hasFa[N];

void dfs(int u)               // 固定根,一遍后序 DFS
{
    f[u][0] = 0;              // 不选 u:先清零
    f[u][1] = r[u];           // 选 u:先加上自己的欢乐值
    for (int v : g[u])        // 逐个孩子合并
    {
        dfs(v);               // ★先把孩子子树算完(后序)
        f[u][0] += max(f[v][0], f[v][1]); // u 不选:孩子随意,各取较大
        f[u][1] += f[v][0];   // u 选了:孩子必须都不选
    }
}

int main()
{
    int n;
    cin >> n;
    for (int i = 1; i <= n; i++)
        cin >> r[i];

    for (int i = 1; i < n; i++)
    {
        int l, k;
        cin >> l >> k;        // l 的上司是 k
        g[k].push_back(l);
        hasFa[l] = true;
    }

    int root = 1;
    while (root <= n && hasFa[root]) root++;  // 没有上司的那个人就是根

    dfs(root);
    cout << max(f[root][0], f[root][1]) << endl;
    return 0;
}
P2016战略游戏SEERC 2000普及/提高-
题意
在树的节点上放士兵,每个士兵能看守与它相连的所有边。求看守全部边所需的最少士兵数。
为什么选它
它是最小点覆盖,与独立集同为 f[u][0/1]f[u][0/1] 却转移方向相反。放在独立集之后学,最能看清「约束在点还是在边」如何决定转移——一次吃透两类模型。
转移 · 复杂度
f[u][0]=∑f[c][1]f[u][0]=\sum f[c][1],f[u][1]=1+∑min⁡(f[c][0],f[c][1])f[u][1]=1+\sum\min(f[c][0],f[c][1]);O(n)O(n)。
参考代码
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

const int N = 1505;
vector<int> g[N];
int f[N][2];                  // f[u][0]:u 不放兵;f[u][1]:u 放兵
bool hasFa[N];

void dfs(int u)
{
    f[u][0] = 0;              // u 不放兵:它的每条边要靠孩子那端守
    f[u][1] = 1;              // u 放兵:+1 个士兵
    for (int v : g[u])
    {
        dfs(v);
        f[u][0] += f[v][1];   // ★u 不放 → 孩子必须放(否则边 u-v 没人看守)
        f[u][1] += min(f[v][0], f[v][1]); // u 放了 → 孩子放不放都行,取较小
    }
}

int main()
{
    int n;
    cin >> n;
    for (int i = 1; i <= n; i++)
    {
        int u, cnt;
        cin >> u >> cnt;      // 洛谷本题为 0-based,读入时 +1 归一到 1-based
        u++;
        while (cnt--)
        {
            int v;
            cin >> v;
            v++;
            g[u].push_back(v);
            hasFa[v] = true;
        }
    }

    int root = 1;
    while (root <= n && hasFa[root]) root++;

    dfs(root);
    cout << min(f[root][0], f[root][1]) << endl;
    return 0;
}

练习

P2458[SDOI2006] 保安站岗最小支配集:不止「选/不选」,还要区分「被孩子覆盖」与「等父亲覆盖」,共三状态。是本部分 cover 类的核心。在洛谷打开
P1122最大子树和f[u] = 含 u 的最大子树权和;孩子贡献为正才接上(max(0, f[c]))。链式合并、无第二维,是选点思想的轻量版。在洛谷打开
P1352没有上司的舞会(自测)把例题不看代码独立写一遍:邻接表建树、找根、后序 DFS 填 f[u][0/1]。手熟这套骨架,后面所有树形 DP 都顺。在洛谷打开

已进入 选点 / 最大独立集 · 树形 DP · DP大师