[模板]树上背包|[CTSC1997] 选课 题解
[CTSC1997] 选课 题解
(符号约定: 课程总数, 选择的课程门数)
树上背包典题,值得深入分析。
图论建模
可以把题目中的选课关系抽象成一棵有根树森林:每个节点代表一门课程,节点的父亲代表这门课程的先修课。为了避免对多个连通块操作带来的不便,可以新增一个虚拟的 号节点作为原森林中所有根节点的父节点,点权为 。由于我们一定会选择 号节点,所以要使 。于是,我们的目标就是:在树上选择一个包含根节点且大小为 的连通块,使得连通块中的点权之和最大。
dp 的初步实现
设 表示在 中选择 个节点的最大收益。我们想从 的子节点转移过来。由于一定会选择 节点,所以要在 的子节点的子树中选择 个节点。
令 的子节点的个数 ,子节点的集合 。假设我们从 的第 个子节点的子树中选择 个节点,那么转移方程即为:
实质上可以把求 的过程看作一个分组背包问题:有 类物品,每类物品有若干个。第 的第 个物品的价值就是 。要从这 类物品中的每一类中选择一个物品,使得总价值最大。
显然,无法直接使用上面的转移方程,因为枚举满足 的数值组合 是困难的。
另辟蹊径,设 表示在 的前 棵子树中选择 个节点的最大收益。这样就可以顺次枚举 的子节点,每次求当前子节点的答案时都可以由前面的结果合并过来。设 表示加入第 个子节点之前 和它的子树构成的连通块的大小(实际上是 ),那么转移方程为:
翻译:枚举 ,从新的子树 中选 个点,从原先的连通块( 与前 个子树形成的)中选 个点,此时的收益为 。
这里枚举的边界很重要。
- :在 中,最少选 个点,最多不能超过 和 。
- :在加入第 个子节点之前 和它的子树构成的连通块中,最少选 个点,最多选 个点。
综上,可以得出 的上界和下界:
(我就是因为边界问题没搞明白,调了一道树上背包一周多……)
初始化:
代码:
void dfs(int u)
{
sz[u] = 1, f[u][0][1] = val[u];
int &cnt = son[u];
for(int v : G[u])
{
cnt++;
dfs(v);
sz[u] += sz[v];
for(int i = 1; i <= min(m, sz[u]); i++)
{
for(int j = max(0, i + sz[v] - sz[u]); j <= min(i-1, sz[v]); j++)
f[u][cnt][i] = max(f[u][cnt][i], f[v][son[v]][j] + f[u][cnt-1][i-j]);
}
}
}
代码中的 sz[u] - sz[v]
就相当于上文中的 。
滚动数组优化
因为 只会从 转移过来,所以可以把第二个维度滚掉。空间复杂度从 降为 。
代码:
void dfs(int u)
{
sz[u] = 1, f[u][0][1] = val[u];
int now = 0, lst;
for(int v : G[u])
{
now ^= 1, lst = now ^ 1, son[u]++;
dfs(v);
sz[u] += sz[v];
for(int i = 1; i <= min(m, sz[u]); i++)
{
f[u][now][i] = 0; // 这里不清零或许也是对的
for(int j = max(0, i + sz[v] - sz[u]); j <= min(i-1, sz[v]); j++)
f[u][now][i] = max(f[u][now][i], f[v][son[v] & 1][j] + f[u][lst][i-j]);
}
}
}
滚动数组优化 II
这次我们可以真正彻底地去掉第二维。
因为 ,所以 。因此可以倒序枚举。
虽然这样做并没有空间复杂度上的优化,但我们确实减少了空间占用。
代码:
void dfs(int u)
{
sz[u] = 1, f[u][1] = val[u];
for(int v : G[u])
{
dfs(v);
sz[u] += sz[v];
for(int i = min(m, sz[u]); i >= 1 ; i--)
{
for(int j = max(0, i + sz[v] - sz[u]); j <= min(i-1, sz[v]); j++)
f[u][i] = max(f[u][i], f[v][j] + f[u][i-j]);
}
}
}
时间复杂度
时间复杂度是 ,但我不会证明。
咕咕咕
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效