1 条题解
-
0
自动搬运
来自洛谷,原作者为

quanjun
**搬运于
2025-08-24 22:36:14,当前版本为作者最后更新于2022-06-27 15:16:41,作者可能在搬运后再次修改,您可在原文处查看最新版自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多
以下是正文
题目链接:https://www.luogu.com.cn/problem/P8127
题目大意:给定一棵包含 个节点的树,树上每个点都有一个权值,节点 的权值为 。每次可以选择树上一个点,将这个点以及与其相邻的所有点的权值取反( 变成 , 变成 )。问:最少需要几次操作能够让树上所有点的权值都变为 ?
我的思路:
首先是树形DP,每个节点 对应 个状态:
- 表示节点权值为 ,没有对节点进行操作时对应的最小操作次数;
- 表示节点权值为 ,没有对节点进行操作时对应的最小操作次数;
- 表示节点权值为 ,有对节点进行操作时对应的最小操作次数;
- 表示节点权值为 ,有对节点进行操作时对应的最小操作次数。
上述所有状态(即 )因为对于节点 的非子孙节点来说,它们是没有办法修改节点 的子孙节点的状态的,所以所有的 对应的状态还包含的一个信息是 —— 节点 的所有子孙节点当前的权值都为 。
同时,这些操作都不考虑父节点的影响(因为我这里的状态都是根据子节点的状态推导当前节点的状态)。
除此之外,我用 来表示状态 不合法。
然后就可以推导所有的状态了。
叶子节点
对于叶子节点 来说:
- 如果 (也就是原始节点的权值为 ),则:
- (因为叶子节点不操作的话无法让节点权值变成 )
- (因为本身节点权值就为 )
- (因为操作一次恰好让节点权值变成 )
- (因为操作一次之后节点权值变成了 ,而不是 )
- 如果 (也就是原始节点的权值为 ),则:
- (因为本身节点权值就为 )
- (因为叶子节点不操作的话无法让节点权值变成 )
- (因为操作一次之后节点权值变成了 ,而不是 )
- (因为操作一次恰好让节点权值变成 )
非叶子节点
对于当前节点 来说,只有可能操作或者不操作。
- 如果选择不操作,则它的子节点的状态必须都是 (设子节点为 ,则此时只跟 、 有关)
- 如果选择操作,则它的子节点的状态必须都是 (设子节点为 ,则此时只跟 、 有关)
但是这里有一个需要思考的问题,就是:当前节点 的状态受子节点中节点的状态的影响!
影响主要在于 —— 子节点中操作过的点是奇数个还是偶数个。
所以可以额外定义四个状态:,这四个状态是对于当前节点 来说的。对于当前节点 ,设其有 个子节点,则:
- 表示节点 的前 个子节点全都是 且有偶数个操作过时对应的最少操作次数(偶数个操作过相当于对节点 的状态没影响,奇数个操作过就会对节点 的状态造成影响);
- 表示节点 的前 个子节点全都是 且有奇数个操作过时对应的最少操作次数;
- 表示节点 的前 个子节点全都是 且有偶数个操作过时对应的最少操作次数;
- 表示节点 的前 个子节点全都是 且有奇数个操作过时对应的最少操作次数
首先:
其次,当 时,(设节点 的第 个子节点为 ,则)有:
- $g_{i,0} = \min \{ g_{i-1,0} + f_{v,0}, g_{i-1,1} + f_{v,2} \}$
- $g_{i,1} = \min \{ g_{i-1,0} + f_{v,2}, g_{i-1,1} + f_{v,0} \}$
- $h_{i,0} = \min \{ h_{i-1,0} + f_{v,1}, h_{i-1,1} + f_{v,3} \}$
- $h_{i,1} = \min \{ h_{i-1,0} + f_{v,3}, h_{i-1,1} + f_{v,1} \}$
当然我们需要的只有最终计算出来的 这四个状态(其中 是当前节点 的子节点个数)。
此时再分别讨论 是 还是 的情况。
当 时:
- (因为本身是 且不操作,要变成 ,所以需要子节点中是 且操作过的数量是奇数个)
- (因为本身就是 且不操作,所以需要子节点中是 且操作过的数量是偶数个)
- (因为本身是 ,操作一次自己变成 ,所以需要子节点是 且操作过的数量是偶数个)
- (因为本身是 ,操作一次会变成 ,所以需要子节点是 且操作过的数量是奇数个)
当 时:
- (因为本身是 且不操作,所以需要子节点中是 且操作过的数量是偶数个)
- (因为本身是 且不操作,要变成 ,所以需要子节点中是 且操作过的数量是奇数个)
- (因为本身是 ,操作一次会变成 ,所以需要子节点是 且操作过的数量是奇数个)
- (因为本身是 ,操作一次自己变成 ,所以需要子节点是 且操作过的数量是偶数个)
示例程序:
#include <bits/stdc++.h> using namespace std; const int maxn = 1e5 + 5; const int INF = (1<<29); int n, a[maxn], f[maxn][4], g[maxn][2], h[maxn][2]; vector<int> e[maxn]; /** f[u][0] 点权为 0,点未操作 f[u][1] 点权为 1,点未操作 f[u][2] 点权为 0,点有操作 f[u][3] 点权为 1,点有操作 */ void dfs(int u, int p) { int sz = e[u].size(); if (sz == 1 && u != 1) { // 叶子节点 if (a[u] == 1) { f[u][0] = INF; f[u][1] = 0; f[u][2] = 1; f[u][3] = INF; } else { // a[u] == 0 f[u][0] = 0; f[u][1] = INF; f[u][2] = INF; f[u][3] = 1; } return; } vector<int> tmp; for (auto v : e[u]) if (v != p) dfs(v, u), tmp.push_back(v); int m = tmp.size(); g[0][0] = h[0][0] = 0; g[0][1] = h[0][1] = INF; for (int i = 1; i <= m; i++) { int v = tmp[i-1]; g[i][0] = min(INF, min(g[i-1][0] + f[v][0], g[i-1][1] + f[v][2])); g[i][1] = min(INF, min(g[i-1][0] + f[v][2], g[i-1][1] + f[v][0])); h[i][0] = min(INF, min(h[i-1][0] + f[v][1], h[i-1][1] + f[v][3])); h[i][1] = min(INF, min(h[i-1][0] + f[v][3], h[i-1][1] + f[v][1])); } if (a[u] == 1) { f[u][0] = g[m][1]; f[u][1] = g[m][0]; f[u][2] = min(INF, 1 + h[m][0]); f[u][3] = min(INF, 1 + h[m][1]); } else { f[u][0] = g[m][0]; f[u][1] = g[m][1]; f[u][2] = min(INF, 1 + h[m][1]); f[u][3] = min(INF, 1 + h[m][0]); } } int main() { scanf("%d", &n); for (int i = 1; i < n; i++) { int u, v; scanf("%d%d", &u, &v); e[u].push_back(v); e[v].push_back(u); } for (int i = 1; i <= n; i++) scanf("%d", a+i); dfs(1, -1); int ans = min(f[1][0], f[1][2]); if (ans == INF) puts("impossible"); else printf("%d\n", ans); return 0; }
- 1
信息
- ID
- 6729
- 时间
- 1000ms
- 内存
- 512MiB
- 难度
- 5
- 标签
- 递交数
- 0
- 已通过
- 0
- 上传者