[CTSC1997] 选课

[CTSC1997] 选课

题目描述

在大学里每个学生,为了达到一定的学分,必须从很多课程里选择一些课程来学习,在课程里有些课程必须在某些课程之前学习,如高等数学总是在其它课程之前学习。现在有 $N$ 门功课,每门课有若干学分,分别记作 $s_1,s_2,\cdots,s_N$,每门课有一门或没有直接先修课(若课程 $a$ 是课程 $b$ 的先修课即只有学完了课程 $a$,才能学习课程 $b$)。一个学生要从这些课程里选择 $M$ 门课程学习,问他能获得的最大学分是多少?

输入格式

第一行有两个整数 $N$,$M$ 用空格隔开 $(1 \leq N \leq 300$ , $1 \leq M \leq 300)$。

接下来的 $N$ 行,第 $i+1$ 行包含两个整数 $k_i$ 和 $s_i$, $k_i$ 表示第 $i$ 门课的直接先修课,$s_i$ 表示第 $i$ 门课的学分。若 $k_i=0$ 表示没有直接先修课 $(0 \leq {k_i} \leq N$,$1 \leq {s_i} \leq 20)$。

输出格式

只有一行,选 $M$ 门课程的最大学分。

输入输出样例 #1

输入 #1

7  4
2  2
0  1
0  4
2  1
7  1
7  6
2  2

输出 #1

13

 

解题思路

  这是一道有依赖的树上背包问题,记录本题主要是为了分析树上背包这类问题的时间复杂度。

  若 $u$ 是 $v$ 的先修课程,则连一条 $u \to v$ 的有向边,这样就会形成一张拓扑图。为统一处理,引入虚拟节点 $0$(其学分 $s_0 = 0$),并向拓扑图中所有入度为 $0$ 的节点连有向边(表示 $0$ 是它们的先修课)。由于除 $0$ 外每个节点恰好有一个父节点,因此该拓扑图转化为以 $0$ 为根的有根树。选择节点 $u$ 则要求必须选择其所有祖先节点,以满足先修条件。引入节点 $0$ 后,问题转化为选择 $m+1$ 个节点(包含必选的节点 $0$)。

  定义状态 $f(u,i,j)$ 表示在以 $u$ 为根的子树中,考虑前 $i$ 棵子树,恰好选择 $j$ 个满足依赖关系的节点所能获得的最大分数。通过合并前 $i$ 棵子树的合并结果与第 $i$ 棵子树来进行状态转移,状态转移方程为 $$f(u,i,j) = \max_{\max\{0, \, j-sz_{u,i-1}\} \leq k \leq \min\{j-1, \, sz_{v_i,c_{v_i}}\}}\left\{ f(u,i-1,j-k) + f(v_i,c_{v_i},k) \right\} \quad \left( 1 \leq i \leq c_{u}, \, 1 \leq j \leq \min\{m+1,sz_{u,i}\} \right)$$

  其中 $v_i$ 是 $u$ 的第 $i$ 个子节点,$c_u$ 是 $u$ 的子节点个数,$sz_{u,i}$ 是以 $u$ 为根、包含其前 $i$ 棵子树的总节点数(包含 $u$ 自身)。初始状态为 $f(u,0,1) = s_u$。最终答案为 $f(0, c_0, m+1)$。

  整个 dp 看似是 $O(nm^2)$ 的,但实际复杂度为 $O(nm)$ 的,这是因为在状态转移中有效的约束条件避免了无效状态的更新。需要注意的是,此优化只有在节点体积均为 $1$ 时成立;若体积非 $1$,复杂度仍为 $O(nm^2)$(子树大小约束失效)。更新:上述划线的内容是错误的!实际上节点的体积为任意非负整数时,复杂度 $O(nm)$ 同样成立,只需把上述定义的 $sz_{u,i}$ 更改为“以 $u$ 为根、包含其前 $i$ 棵子树节点的总体积”即可,代入到下述的分析是一样的。

  下面给出证明,先证明时间复杂度的上限是 $O(n^2)$(忽略 $m$ 的约束),对应的转移方程为($j$ 的范围变为 $1 \leq j \leq sz_{u,i}$)$$f(u,i,j) = \max_{\max\{0, \, j-sz_{u,i-1}\} \leq k \leq \min\{j-1, \, sz_{v_i,c_{v_i}}\}}\left\{ f(u,i-1,j-k) + f(v_i,c_{v_i},k) \right\} \quad \left( 1 \leq i \leq c_{u}, \, 1 \leq j \leq sz_{u,i} \right)$$

  设 $T(u)$ 表示处理以 $u$ 为根的子树所需时间,有 $$\begin{align*} T(u) &= 1 + \sum_{i=1}^{c_u} \left[ T(v_i) + sz_{u,i} \times sz_{v_i,c_{v_i}} \right] \\ &= 1 + \sum_{i=1}^{c_u} T(v_i) + \sum_{i=1}^{c_u} (sz_{u,i} \times sz_{v_i,c_{v_i}}) \\ &= 1 + \sum_{i=1}^{c_u} T(v_i) + t_u \end{align*}$$

  其中 $t_u = \sum_{i=1}^{c_u} (sz_{u,i-1} \times sz_{v_i,c_{v_i}})$,其组合意义为:从 $u$ 的不同子树中选两个节点的方案数(等价于在 $u$ 的子树中任选两节点方案数,减去在同一子树中选两节点的方案数),因此有 $$t_u = \binom{sz_{u,c_u}}{2} - \sum_{i=1}^{c_u}{\binom{sz_{v_i,c_{v_i}}}{2}}$$

  考虑边界情况,当 $u$ 为叶子,有 $T(u) = 1 + t_u$,其中 $t_u = 0$。对于以 $1$ 为根大小为 $n$ 的树,那么处理整棵树的总时间为 $T(1)$,可以通过模拟 BFS 的过程依次将 $T(v_i)$ 代入计算,最终得到 $T(1) = n + \sum\limits_{i=1}^{n}{t_u}$。再将 $t_u = \binom{sz_{u,c_u}}{2} - \sum_{i=1}^{c_u}{\binom{sz_{v_i,c_{v_i}}}{2}}$ 代入,通过模拟 BFS 过程继续计算 $\sum\limits_{i=1}^{n}{t_u}$ ,最终得到 $\sum\limits_{i=1}^{n}{t_u} = \binom{sz_{u,c_u}}{2}$。因此 $T(1) = n + \binom{sz_{1,c_1}}{2} = n + \binom{n}{2}$,故计算量为 $O(n^2)$,得证。

  接下来证明更紧的上界 $O(nm)$(考虑 $m \leq n$ 的约束),对应的转移方程为($1 \leq j \leq \min\{m, sz_{u,i}\}$)$$f(u,i,j) = \max_{\max\{0, \, j-sz_{u,i-1}\} \leq k \leq \min\{j-1, \, sz_{v_i,c_{v_i}}\}}\left\{ f(u,i-1,j-k) + f(v_i,c_{v_i},k) \right\} \quad \left( 1 \leq i \leq c_{u}, \, 1 \leq j \leq \min\{m,sz_{u,i}\} \right)$$

  使用归纳法。

  归纳基础:若 $u$ 是叶子,则有 $T(u) = O(1) = O(1 \times m) = O(sz_{u,c_u} \times m)$。

  归纳假设:对于 $u$ 的子节点 $v_i$,假设有 $T(v_i) = O(sz_{v_i,c_{v_i}} \times m)$。

  归纳步骤:对于非叶子节点 $u$,有 $T(u) = 1 + \sum\limits_{i=1}^{c_u}{T(v_i)} + t_u$,其中 $t_u = \sum\limits_{i=1}^{c_u}\left( \min\left\{ m, sz_{u,i} \right\} \times \min\{ m, sz_{v_i,c_{v_i}} \} \right)$。

  由于 $\min\left\{ m, sz_{u,i} \right\} \leq m$,$\min\{ m, sz_{v_i,c_{v_i}} \} \leq sz_{v_i,c_{v_i}}$,因此 $t_u \leq \sum\limits_{i=1}^{c_u}{m \times sz_{v_i,c_{v_i}}} = m \sum\limits_{i=1}^{c_u}{sz_{v_i,c_{v_i}}} = sz_{u,c_u} \times m$,所以 $t_u$ 的复杂度为 $O(sz_{u,c_u} \times m)$。结合归纳假设, $\sum\limits_{i=1}^{c_u}{T(v_i)}$ 的复杂度等于 $O\left( \sum\limits_{i=1}^{c_u}sz_{v_i,c_{v_i}} \times m \right) = O(sz_{u,c_u} \times m)$,因此 $T(u)$ 的复杂度为 $O( sz_{u,c_u} \times m)$,归纳成立。

  因此对于以 $1$ 为根大小为 $n$ 的树,总复杂度 $T(1)$ 为 $O(nm)$,得证。

  在代码实现中参考背包问题优化掉了上述状态 $f(u,i,j)$ 的第二维,在枚举 $i$ 时需要倒序枚举来进行状态转移。

  AC 代码如下,时间复杂度为 $O(nm)$:

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

typedef long long LL;

const int N = 305;

int n, m;
int a[N];
int h[N], e[N], ne[N], idx;
int f[N][N];

void add(int u, int v) {
    e[idx] = v, ne[idx] = h[u], h[u] = idx++;
}

int dfs(int u) {
    f[u][1] = a[u];
    int s = 1;
    for (int i = h[u]; i != -1; i = ne[i]) {
        int v = e[i];
        int t = dfs(v);
        s += t;
        for (int j = min(m + 1, s); j; j--) {
            for (int k = max(0, j - s + t); k < j && k <= t; k++) {
                f[u][j] = max(f[u][j], f[u][j - k] + f[v][k]);
            }
        }
    }
    return s;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cin >> n >> m;
    memset(h, -1, sizeof(h));
    for (int i = 1; i <= n; i++) {
        int x;
        cin >> x >> a[i];
        add(x, i);
    }
    dfs(0);
    cout << f[0][m + 1];
    
    return 0;
}

 

参考资料

  树上背包的上下界优化:https://www.cnblogs.com/ouuan/p/BackpackOnTree.html

posted @ 2025-08-04 14:15  onlyblues  阅读(7)  评论(0)    收藏  举报
Web Analytics