树上交换节点
树上交换节点
OPPO 笔试真题 · 2023 秋招 · 后端岗
判题请到牛客原题提交。
题目描述
给定一棵树,每个节点有一个权值。现在每次可以交换任意两个节点的权值,请问最少多少次交换可以使得每个节点的权值等于它的编号?
保证给出的权值是一个排列,也就是说保证一定有解。
输入格式
第一行输入一个正整数,代表树上的节点数量。
第二行输入个正整数,第个正整数是号节点的权值,互不相同。
接下来的行,每行输入两个正整数和,代表号节点和号节点有一条边相连。
输出格式
一个整数,代表最小的交换次数。
示例
示例 1
输入:
4
2 1 4 3
1 2
2 3
2 4输出:
2说明:
第一次交换1号节点和2号节点的权值,四个点的权值变成[1,2,4,3]
第二次交换3号节点和4号节点的权值,四个点的权值变成[1,2,3,4]
解题思路
第一步:问题本质是「置换」
每个结点 上放着权值 ,而 是 的排列。
把「结点 上放着权值 」看成一条映射 ,
它必然分解成若干个互不相交的环。
目标状态是「结点 上放 」,也就是把每个环都拆成自环。
第二步:一个长度为 L 的环需要 L-1 次交换
以环 为例():
- 交换结点 1 和结点 3 上的权值,结点 1 归位;
- 交换结点 2 和结点 3 上的权值,结点 2、3 同时归位。
共 2 次 = 。推广开来:每交换一次最多让「已归位的结点数」增加 1,
而 个结点的环最后一步能同时归位两个,所以下界是 ;
上面的做法恰好达到下界。
长度 1 的环()本来就是自环,需要 0 次。
第三步:总次数 = n - 环的个数
所以只要求出置换的环数。
第四步:树的结构完全用不上
题目允许交换任意两个结点的权值,与这两个结点在树上是否相邻无关。
给出的 条边只是把题面包装成一棵树,读入后直接忽略即可 ——
这是本题最容易想复杂的地方。
完整代码实现
import sys
def solve() -> None:
"""置换分解成环,答案 = n - 环数;树的边读掉即可,不参与计算"""
data = sys.stdin.buffer.read().split()
if not data:
return
pos = 0
n = int(data[pos]); pos += 1
perm = [0] * (n + 1)
for i in range(1, n + 1):
perm[i] = int(data[pos]); pos += 1
# 后面的 n-1 条边与答案无关,直接跳过(token 已经全部读进 data 里)
visited = [False] * (n + 1)
cycles = 0
for i in range(1, n + 1):
if not visited[i]:
cycles += 1
j = i
while not visited[j]: # 顺着置换走完整个环
visited[j] = True
j = perm[j]
sys.stdout.write(str(n - cycles) + "\n")
if __name__ == "__main__":
solve()示例推演
输入 a = [2, 1, 4, 3](1 起编号):
| 起点 | 走出的环 | 长度 |
|---|---|---|
| 1 | 2 | |
| 3 | 2 |
环数是 2,答案 。
具体操作(与题面一致):
- 交换结点 1、2 →
[1,2,4,3]; - 交换结点 3、4 →
[1,2,3,4]。
每个二元环用 1 次交换,合计 2 次。
复杂度分析
| 解法 | 时间复杂度 | 空间复杂度 | 说明 |
|---|---|---|---|
| 暴力搜索交换方案 | 指数级 | 指数级 | 状态是 个排列,不可行 |
| 置换找环 | 推荐,每个结点只走一次 |
易错点总结
1. 被「树」误导
题面给了树,但交换不受边的限制。如果误以为只能交换树上相邻的结点,
问题会变成完全不同的「树上 token swapping」,答案也会不一样。
判断依据是这句话:「每次可以交换任意两个节点的权值」。
2. 忘记减去环数
答案是 :不是 ,也不是环的个数本身。长度为 1 的环
贡献 0,公式会自动处理。
3. 环没走完就换起点
必须在 while not visited[j] 里顺着 一路走到底,把整个环都标记完,
再去看下一个未访问的点。中途跳出会把一个环数成多个。
4. 下标从 0 开始混用
结点编号是 ,visited 要开 ,循环从 1 到 。
写成 0 起点会错位一格。
5. 认为「已经有序」还要特殊判断
当所有 时,每个点都是长度 1 的环,环数 ,
答案 ,公式本身就能覆盖,不需要额外分支。
扩展思考
- 若只允许交换树上相邻结点的权值,问题变成「树上 token swapping」,
一般图上是 NP-hard,树上也有专门的下界与算法,和本题完全不同。 - 若允许一次交换三个结点的权值(轮换),一个长度 的环只需要
次左右,答案会改变。 - 「 减去环数」这个结论也适用于「把排列变成另一个排列」:
先把目标排列复合进去,再数环即可。