2 条题解

  • 0
    @ 2025-10-23 0:05:00

    题目分析

    本题要求对一棵二叉点权(0或1)的树执行最少的切换操作,使所有节点的点权变为0。切换操作的定义是:对节点i操作后,i及其所有直接相邻节点的点权取反(0变1,1变0)。

    核心挑战在于:

    • 每个操作会影响多个节点(当前节点+相邻节点)
    • 操作顺序不影响最终结果(取反操作具有交换性)
    • 需要找到最少操作次数或判断无解

    核心思路

    由于树是连通无环图,适合采用贪心策略结合遍历算法求解:

    1. 状态定义

      • 对于每个节点,有两种状态:操作(1)或不操作(0)
      • 每个节点的最终状态由初始状态和被操作的次数(奇偶性)决定
    2. 关键观察

      • 树的遍历具有层次性(父节点与子节点的依赖关系)
      • 对于已处理的父节点,子节点的操作状态可由父节点的当前状态唯一确定
    3. 算法设计

      • 选择一个根节点(如节点1)
      • 采用BFS或DFS遍历树,从根节点向下处理
      • 对于每个节点,根据其父节点的当前状态决定是否需要操作

    具体实现

    1. 两种初始情况

      • 根节点不操作
      • 根节点操作 (因为根节点没有父节点,其操作状态无法由其他节点决定,需枚举两种情况)
    2. 遍历处理

      • 维护每个节点的当前状态(受父节点和自身操作影响)
      • 对每个子节点,若父节点当前状态为1,则必须操作子节点以抵消影响
      • 记录操作次数,最后检查所有节点是否都变为0
    3. 结果选择

      • 两种初始情况中,取有效解的最少操作次数
      • 若两种情况都无效,则输出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),用于存储树的邻接表、节点状态和访问标记

    关键优化

    1. 贪心策略:通过父节点状态决定子节点是否操作,避免了枚举所有可能的操作组合
    2. 状态维护:只需要跟踪当前状态,不需要记录操作历史
    3. 两种初始情况:仅需枚举根节点的两种可能状态,大大减少了计算量

    该算法高效解决了树结构上的切换操作问题,能够处理N=10^5的大规模输入。

    • 0
      @ 2025-10-19 19:04:14

      题目分析

      我们有一棵 NN 个节点的树,每个节点初始值为 0011。每次操作可以选择一个节点,翻转该节点及其所有邻居节点的值(00111100)。目标是让所有节点都变为 00,求最小操作次数。

      关键观察

      1. 操作的可交换性:操作顺序不影响最终结果
      2. 操作的幂等性:对同一节点操作两次等于没操作,所以每个节点最多操作一次
      3. 影响范围:操作一个节点会影响自身和所有邻居

      树形 DP 解法

      状态设计

      dp[u][x][y]dp[u][x][y] 表示:

      • uu:当前节点
      • xx:父节点是否被操作过(00:否,11:是)
      • yy:当前节点是否被操作(00:否,11:是)

      状态值表示在满足条件下,以 uu 为根的子树需要的最小操作次数。

      状态转移

      对于节点 uu,我们需要考虑:

      1. 当前节点的最终值必须为 00
      2. 当前节点的值受到以下因素影响:
        • 初始值 a[u]a[u]
        • 父节点操作的影响(如果 x=1x = 1
        • 自身操作的影响(如果 y=1y = 1
        • 所有子节点操作的影响

      设当前节点 uu 的受影响后的值为:

      val=a[u]xyval = a[u] \oplus x \oplus y

      对于每个子节点 vvuu 的操作会影响 vv(如果 y=1y = 1),同时 vv 的操作也会影响 uu

      我们需要枚举所有子节点的状态,使得 uu 的最终值为 00

      算法步骤

      1. 建树,存储邻接表
      2. 进行 DFS,从叶子节点向上计算 DP 值
      3. 对于每个节点,枚举父节点状态和自身操作状态
      4. 对于每种状态组合,计算子节点的最优组合
      5. 最终答案为根节点的某种状态的最小值

      代码实现

      #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;
      }
      

      复杂度分析

      • 时间复杂度O(N)O(N),每个节点处理常数次状态转移
      • 空间复杂度O(N)O(N),存储树结构和DP数组

      算法要点

      1. 状态设计:三维状态记录父节点和当前节点的操作状态
      2. 状态转移:枚举子节点状态,确保当前节点最终值为 00
      3. 边界处理:叶子节点的特殊情况
      4. 答案提取:根节点在父节点未操作情况下的最小值

      总结

      本题是典型的树形动态规划问题,关键在于设计合适的状态表示和转移方程。通过分析操作的影响范围和树的结构特性,我们可以高效地求解最小操作次数。

      • 1

      信息

      ID
      3449
      时间
      3000ms
      内存
      1024MiB
      难度
      10
      标签
      递交数
      8
      已通过
      1
      上传者