Loading docs/dp/tree.md +8 −8 Original line number Diff line number Diff line Loading @@ -16,7 +16,7 @@ - $f(i,0) = \sum\max \{f(x,1),f(x,0)\}$ (上司不参加舞会时,下属可以参加,也可以不参加) - $f(i,1) = \sum{f(x,0)} + a_i$ (上司参加舞会时,下属都不会参加) 我们可以通过 DFS,在返回上一层时更新当前节点的最优解。 我们可以通过 DFS,在返回上一层时更新当前结点的最优解。 代码: Loading @@ -35,7 +35,7 @@ void addedge(int u, int v) { } void calc(int k) { vis[k] = 1; for (int i = head[k]; i; i = e[i].next) { // 枚举该节点的每个子节点 for (int i = head[k]; i; i = e[i].next) { // 枚举该结点的每个子结点 if (vis[e[i].v]) continue; calc(e[i].v); f[k][1] += f[e[i].v][0]; Loading @@ -53,7 +53,7 @@ int main() { addedge(k, l); } for (int i = 1; i <= n; i++) if (!is_h[i]) { // 从根节点开始DFS if (!is_h[i]) { // 从根结点开始DFS calc(i); printf("%d", max(f[i][1], f[i][0])); return 0; Loading @@ -80,13 +80,13 @@ int main() { $n,m \leq 300$ 每门课最多只有一门先修课的特点,与有根树中一个点最多只有一个父亲节点的特点类似。 每门课最多只有一门先修课的特点,与有根树中一个点最多只有一个父亲结点的特点类似。 因此可以想到根据这一性质建树,从而所有课程组成了一个森林的结构。为了方便起见,我们可以新增一门 $0$ 学分的课程(设这个课程的编号为 $0$ ),作为所有无先修课课程的先修课,这样我们就将森林变成了一棵以 $0$ 号课程为根的树。 我们设 $f(u,i,j)$ 表示以 $u$ 号点为根的子树中,已经遍历了 $u$ 号点的前 $i$ 棵子树,选了 $j$ 门课程的最大学分。 转移的过程结合了树形 DP 和背包 DP 的特点,我们枚举 $u$ 点的每个子节点 $v$ ,同时枚举以 $v$ 为根的子树选了几门课程,将子树的结果合并到 $u$ 上。 转移的过程结合了树形 DP 和背包 DP 的特点,我们枚举 $u$ 点的每个子结点 $v$ ,同时枚举以 $v$ 为根的子树选了几门课程,将子树的结果合并到 $u$ 上。 记点 $x$ 的儿子个数为 $s_x$ ,以 $x$ 为根的子树大小为 $\textit{siz_x}$ ,很容易写出下面的转移方程: Loading Loading @@ -145,7 +145,7 @@ $$ ## 换根DP 树形 DP 中的换根 DP 问题又被称为二次扫描,通常不会指定根节点,并且根节点的变化会对一些值,例如子节点深度和、点权和等产生影响。 树形 DP 中的换根 DP 问题又被称为二次扫描,通常不会指定根结点,并且根结点的变化会对一些值,例如子结点深度和、点权和等产生影响。 通常需要两次 DFS ,第一次 DFS 预处理诸如深度,点权和之类的信息,在第二次 DFS 开始运行换根动态规划。 Loading @@ -154,7 +154,7 @@ $$ ???+note "例题[[POI2008]STA-Station](https://www.luogu.com.cn/problem/P3478)" 给定一个 $n$ 个点的树,请求出一个结点,使得以这个结点为根时,所有结点的深度之和最大。 不妨令$u$为当前结点,$v$为当前节点的子节点。首先需要用$s_i$来表示以 $i$ 为根的子树中的结点个数,并且有$s_u=\sum s_v$。显然需要一次 DFS 来计算所有的$s_i$,这次的 DFS 就是预处理,我们得到了以某个结点为根时其子树中的结点总数。 不妨令$u$为当前结点,$v$为当前结点的子结点。首先需要用$s_i$来表示以 $i$ 为根的子树中的结点个数,并且有$s_u=\sum s_v$。显然需要一次 DFS 来计算所有的$s_i$,这次的 DFS 就是预处理,我们得到了以某个结点为根时其子树中的结点总数。 考虑状态转移,这里就是体现"换根"的地方了。令$f_u$为以$u$为根时,所有结点的深度之和。 Loading @@ -166,7 +166,7 @@ $f_v\leftarrow f_u$可以体现换根,即以$u$为根转移到以$v$为根。 根据这两个条件就可以推出状态转移方程$f_v = f_u - s_v + n - s_v=f_u + n - 2 \times s_v$。 于是在第二次 DFS 遍历整棵树并状态转移$f_v=f_u + n - 2 \times s_v$,那么就能求出以每个结点为根时的深度和了。最后只需要遍历一次所有根节点深度和就可以求出答案。 于是在第二次 DFS 遍历整棵树并状态转移$f_v=f_u + n - 2 \times s_v$,那么就能求出以每个结点为根时的深度和了。最后只需要遍历一次所有根结点深度和就可以求出答案。 ??? note "参考代码" ```cpp Loading Loading
docs/dp/tree.md +8 −8 Original line number Diff line number Diff line Loading @@ -16,7 +16,7 @@ - $f(i,0) = \sum\max \{f(x,1),f(x,0)\}$ (上司不参加舞会时,下属可以参加,也可以不参加) - $f(i,1) = \sum{f(x,0)} + a_i$ (上司参加舞会时,下属都不会参加) 我们可以通过 DFS,在返回上一层时更新当前节点的最优解。 我们可以通过 DFS,在返回上一层时更新当前结点的最优解。 代码: Loading @@ -35,7 +35,7 @@ void addedge(int u, int v) { } void calc(int k) { vis[k] = 1; for (int i = head[k]; i; i = e[i].next) { // 枚举该节点的每个子节点 for (int i = head[k]; i; i = e[i].next) { // 枚举该结点的每个子结点 if (vis[e[i].v]) continue; calc(e[i].v); f[k][1] += f[e[i].v][0]; Loading @@ -53,7 +53,7 @@ int main() { addedge(k, l); } for (int i = 1; i <= n; i++) if (!is_h[i]) { // 从根节点开始DFS if (!is_h[i]) { // 从根结点开始DFS calc(i); printf("%d", max(f[i][1], f[i][0])); return 0; Loading @@ -80,13 +80,13 @@ int main() { $n,m \leq 300$ 每门课最多只有一门先修课的特点,与有根树中一个点最多只有一个父亲节点的特点类似。 每门课最多只有一门先修课的特点,与有根树中一个点最多只有一个父亲结点的特点类似。 因此可以想到根据这一性质建树,从而所有课程组成了一个森林的结构。为了方便起见,我们可以新增一门 $0$ 学分的课程(设这个课程的编号为 $0$ ),作为所有无先修课课程的先修课,这样我们就将森林变成了一棵以 $0$ 号课程为根的树。 我们设 $f(u,i,j)$ 表示以 $u$ 号点为根的子树中,已经遍历了 $u$ 号点的前 $i$ 棵子树,选了 $j$ 门课程的最大学分。 转移的过程结合了树形 DP 和背包 DP 的特点,我们枚举 $u$ 点的每个子节点 $v$ ,同时枚举以 $v$ 为根的子树选了几门课程,将子树的结果合并到 $u$ 上。 转移的过程结合了树形 DP 和背包 DP 的特点,我们枚举 $u$ 点的每个子结点 $v$ ,同时枚举以 $v$ 为根的子树选了几门课程,将子树的结果合并到 $u$ 上。 记点 $x$ 的儿子个数为 $s_x$ ,以 $x$ 为根的子树大小为 $\textit{siz_x}$ ,很容易写出下面的转移方程: Loading Loading @@ -145,7 +145,7 @@ $$ ## 换根DP 树形 DP 中的换根 DP 问题又被称为二次扫描,通常不会指定根节点,并且根节点的变化会对一些值,例如子节点深度和、点权和等产生影响。 树形 DP 中的换根 DP 问题又被称为二次扫描,通常不会指定根结点,并且根结点的变化会对一些值,例如子结点深度和、点权和等产生影响。 通常需要两次 DFS ,第一次 DFS 预处理诸如深度,点权和之类的信息,在第二次 DFS 开始运行换根动态规划。 Loading @@ -154,7 +154,7 @@ $$ ???+note "例题[[POI2008]STA-Station](https://www.luogu.com.cn/problem/P3478)" 给定一个 $n$ 个点的树,请求出一个结点,使得以这个结点为根时,所有结点的深度之和最大。 不妨令$u$为当前结点,$v$为当前节点的子节点。首先需要用$s_i$来表示以 $i$ 为根的子树中的结点个数,并且有$s_u=\sum s_v$。显然需要一次 DFS 来计算所有的$s_i$,这次的 DFS 就是预处理,我们得到了以某个结点为根时其子树中的结点总数。 不妨令$u$为当前结点,$v$为当前结点的子结点。首先需要用$s_i$来表示以 $i$ 为根的子树中的结点个数,并且有$s_u=\sum s_v$。显然需要一次 DFS 来计算所有的$s_i$,这次的 DFS 就是预处理,我们得到了以某个结点为根时其子树中的结点总数。 考虑状态转移,这里就是体现"换根"的地方了。令$f_u$为以$u$为根时,所有结点的深度之和。 Loading @@ -166,7 +166,7 @@ $f_v\leftarrow f_u$可以体现换根,即以$u$为根转移到以$v$为根。 根据这两个条件就可以推出状态转移方程$f_v = f_u - s_v + n - s_v=f_u + n - 2 \times s_v$。 于是在第二次 DFS 遍历整棵树并状态转移$f_v=f_u + n - 2 \times s_v$,那么就能求出以每个结点为根时的深度和了。最后只需要遍历一次所有根节点深度和就可以求出答案。 于是在第二次 DFS 遍历整棵树并状态转移$f_v=f_u + n - 2 \times s_v$,那么就能求出以每个结点为根时的深度和了。最后只需要遍历一次所有根结点深度和就可以求出答案。 ??? note "参考代码" ```cpp Loading