📜  门|门CS 2013 |第 31 题(1)

📅  最后修改于: 2023-12-03 15:28:49.132000             🧑  作者: Mango

题目介绍

本题为“门|门CS 2013”第31题,题目难度为普及/提高-。 题目描述如下:

给你一棵 $n$ 个节点的树,节点编号从 $1$ 到 $n$,现在你需要把这棵树划分为若干个连通块,使得每个连通块包含的节点编号都连续。求最少需要划分成多少个连通块。

注意事项

  • $2\leq n\leq 10^6$
  • 时限:2s
  • 空间限制:128MB

实现思路

本题有多种实现思路,以下将分别介绍它们。

实现思路一

这是一种比较暴力的做法,对于每个节点,从它的父节点开始往下走,一直到第一个叶子结点,这一整条路径都是一个连通块。 具体实现时,可以采用深度优先搜索(DFS)的方式遍历整棵树,每次遍历到一个叶子结点,就将路径上的点打上标记。 最后,遍历完整棵树后,将没有被标记的点记为一个新的连通块。 时间复杂度约为 $O(n\log n)$。

实现思路二

这是一种稍微高效一些的做法。思路是对于每个节点,求出以它为根的子树中,编号最小和最大的叶子结点,这个叶子结点当做一个连通块。 具体实现时,可以采用递归的方式,遍历整棵树,每次递归到一个节点,将它与它的孩子的编号最小和最大值进行比较,如果左右子树的编号范围均连续,则视为同一个连通块。 因为只需要递归遍历一遍树,所以时间复杂度约为 $O(n)$。

实现思路三

这是一种比较高效的做法。它的思路是对树进行序列化,横向遍历节点,根据节点的连续性,将它们划分为若干个连通块。 具体实现时,可以使用广度优先搜索(BFS)遍历树,遍历到每个节点时,将它与它的兄弟节点进行比较,如果兄弟节点编号连续,则视为同一个连通块。如果不连续,则将上一个连通块结束,并开始下一个连通块。最后,将没有被包含在任何一个连通块中的节点单独记为一个连通块即可。 时间复杂度约为 $O(n)$。

参考代码

以下为实现思路三的代码:

from collections import deque

def solve():
    n = int(input())
    adj = [[] for _ in range(n + 1)]
    for _ in range(n - 1):
        u, v = map(int, input().split())
        adj[u].append(v)
        adj[v].append(u)
    q = deque([(1, 0)])  # 层级遍历的队列
    ans = 1  # 初始连通块数量
    last = 1  # 记录上一次结束的位置
    while q:
        node, last_id = q.popleft()
        for child in adj[node]:
            if child == last_id:
                continue
            if child != last + 1:
                ans += 1
                last = node
            q.append((child, node))
    print(ans)  

以上为Python 3的代码,采用了队列实现BFS遍历。时间复杂度为 $O(n)$ 的同时,空间复杂度为 $O(n)$。