2 条题解
-
0
题目分析
本题要求对一棵二叉点权(0或1)的树执行最少的切换操作,使所有节点的点权变为0。切换操作的定义是:对节点i操作后,i及其所有直接相邻节点的点权取反(0变1,1变0)。
核心挑战在于:
- 每个操作会影响多个节点(当前节点+相邻节点)
- 操作顺序不影响最终结果(取反操作具有交换性)
- 需要找到最少操作次数或判断无解
核心思路
由于树是连通无环图,适合采用贪心策略结合遍历算法求解:
-
状态定义:
- 对于每个节点,有两种状态:操作(1)或不操作(0)
- 每个节点的最终状态由初始状态和被操作的次数(奇偶性)决定
-
关键观察:
- 树的遍历具有层次性(父节点与子节点的依赖关系)
- 对于已处理的父节点,子节点的操作状态可由父节点的当前状态唯一确定
-
算法设计:
- 选择一个根节点(如节点1)
- 采用BFS或DFS遍历树,从根节点向下处理
- 对于每个节点,根据其父节点的当前状态决定是否需要操作
具体实现
-
两种初始情况:
- 根节点不操作
- 根节点操作 (因为根节点没有父节点,其操作状态无法由其他节点决定,需枚举两种情况)
-
遍历处理:
- 维护每个节点的当前状态(受父节点和自身操作影响)
- 对每个子节点,若父节点当前状态为1,则必须操作子节点以抵消影响
- 记录操作次数,最后检查所有节点是否都变为0
-
结果选择:
- 两种初始情况中,取有效解的最少操作次数
- 若两种情况都无效,则输出impossible
代码实现
import sys from collections import deque def solve(): N = int(sys.stdin.readline()) edges = [[] for _ in range(N+1)] # 1-based for _ in range(N-1): u, v = map(int, sys.stdin.readline().split()) edges[u].append(v) edges[v].append(u) a = list(map(int, sys.stdin.readline().split())) a = [0] + a # 转为1-based # 尝试两种初始状态:根节点是否操作 def check(root_op): state = a.copy() visited = [False] * (N+1) q = deque() q.append(1) visited[1] = True ops = 0 # 根节点的初始操作 if root_op: ops += 1 state[1] ^= 1 # 自身取反 for v in edges[1]: state[v] ^= 1 # 相邻节点取反 while q: u = q.popleft() for v in edges[u]: if not visited[v]: visited[v] = True q.append(v) # 根据父节点u的当前状态决定是否操作v if state[u] == 1: # 需要操作v来抵消u的1状态 ops += 1 state[v] ^= 1 # 同时影响v的相邻节点(包括u) for w in edges[v]: state[w] ^= 1 # 检查所有节点是否都为0 for i in range(1, N+1): if state[i] != 0: return float('inf') return ops # 计算两种初始情况的结果 res1 = check(0) # 根节点不操作 res2 = check(1) # 根节点操作 ans = min(res1, res2) if ans == float('inf'): print("impossible") else: print(ans) if __name__ == "__main__": solve()复杂度分析
- 时间复杂度:O(N),两种初始情况各需一次BFS遍历树,每次遍历复杂度为O(N)
- 空间复杂度:O(N),用于存储树的邻接表、节点状态和访问标记
关键优化
- 贪心策略:通过父节点状态决定子节点是否操作,避免了枚举所有可能的操作组合
- 状态维护:只需要跟踪当前状态,不需要记录操作历史
- 两种初始情况:仅需枚举根节点的两种可能状态,大大减少了计算量
该算法高效解决了树结构上的切换操作问题,能够处理N=10^5的大规模输入。
-
0
题目分析
我们有一棵 个节点的树,每个节点初始值为 或 。每次操作可以选择一个节点,翻转该节点及其所有邻居节点的值( 变 , 变 )。目标是让所有节点都变为 ,求最小操作次数。
关键观察
- 操作的可交换性:操作顺序不影响最终结果
- 操作的幂等性:对同一节点操作两次等于没操作,所以每个节点最多操作一次
- 影响范围:操作一个节点会影响自身和所有邻居
树形 DP 解法
状态设计
设 表示:
- :当前节点
- :父节点是否被操作过(:否,:是)
- :当前节点是否被操作(:否,:是)
状态值表示在满足条件下,以 为根的子树需要的最小操作次数。
状态转移
对于节点 ,我们需要考虑:
- 当前节点的最终值必须为
- 当前节点的值受到以下因素影响:
- 初始值
- 父节点操作的影响(如果 )
- 自身操作的影响(如果 )
- 所有子节点操作的影响
设当前节点 的受影响后的值为:
对于每个子节点 , 的操作会影响 (如果 ),同时 的操作也会影响 。
我们需要枚举所有子节点的状态,使得 的最终值为 。
算法步骤
- 建树,存储邻接表
- 进行 DFS,从叶子节点向上计算 DP 值
- 对于每个节点,枚举父节点状态和自身操作状态
- 对于每种状态组合,计算子节点的最优组合
- 最终答案为根节点的某种状态的最小值
代码实现
#include <bits/stdc++.h> using namespace std; const int INF = 1e9; const int MAXN = 1e5 + 5; int n; vector<int> graph[MAXN]; int a[MAXN]; int dp[MAXN][2][2]; // dp[u][x][y]: 父节点操作x,当前节点操作y void dfs(int u, int parent) { // 初始化DP数组 for (int x = 0; x < 2; x++) { for (int y = 0; y < 2; y++) { dp[u][x][y] = INF; } } // 当前节点受影响的最终值 for (int x = 0; x < 2; x++) { for (int y = 0; y < 2; y++) { int current_val = a[u] ^ x ^ y; // 如果是叶子节点 if (graph[u].size() == 1 && u != 1) { if (current_val == 0) { dp[u][x][y] = y; } continue; } // 处理子节点 vector<vector<int>> child_dp; for (int v : graph[u]) { if (v == parent) continue; dfs(v, u); child_dp.push_back({dp[v][y][0], dp[v][y][1]}); } // 动态规划处理子节点组合 int m = child_dp.size(); vector<vector<int>> f(m + 1, vector<int>(2, INF)); f[0][current_val] = 0; for (int i = 0; i < m; i++) { for (int state = 0; state < 2; state++) { if (f[i][state] == INF) continue; // 子节点不操作 int new_state = state ^ 0; int cost = child_dp[i][0]; if (cost != INF) { f[i + 1][new_state] = min(f[i + 1][new_state], f[i][state] + cost); } // 子节点操作 new_state = state ^ 1; cost = child_dp[i][1]; if (cost != INF) { f[i + 1][new_state] = min(f[i + 1][new_state], f[i][state] + cost); } } } if (f[m][0] != INF) { dp[u][x][y] = f[m][0] + y; } } } } int main() { ios::sync_with_stdio(false); cin.tie(0); cin >> n; for (int i = 0; i < n - 1; i++) { int u, v; cin >> u >> v; graph[u].push_back(v); graph[v].push_back(u); } for (int i = 1; i <= n; i++) { cin >> a[i]; } // 以1为根进行DFS dfs(1, 0); int ans = INF; for (int y = 0; y < 2; y++) { ans = min(ans, dp[1][0][y]); } if (ans == INF) { cout << "impossible" << endl; } else { cout << ans << endl; } return 0; }复杂度分析
- 时间复杂度:,每个节点处理常数次状态转移
- 空间复杂度:,存储树结构和DP数组
算法要点
- 状态设计:三维状态记录父节点和当前节点的操作状态
- 状态转移:枚举子节点状态,确保当前节点最终值为
- 边界处理:叶子节点的特殊情况
- 答案提取:根节点在父节点未操作情况下的最小值
总结
本题是典型的树形动态规划问题,关键在于设计合适的状态表示和转移方程。通过分析操作的影响范围和树的结构特性,我们可以高效地求解最小操作次数。
- 1
信息
- ID
- 3449
- 时间
- 3000ms
- 内存
- 1024MiB
- 难度
- 10
- 标签
- 递交数
- 8
- 已通过
- 1
- 上传者