1. 问题描述:
给定 n 本书,编号为 1∼n。在初始状态下,书是任意排列的。在每一次操作中,可以抽取其中连续的一段,再把这段插入到其他某个位置。我们的目标状态是把书按照 1∼n 的顺序依次排列。求最少需要多少次操作。
输入格式
第一行包含整数 T,表示共有 T 组测试数据。每组数据包含两行,第一行为整数 n,表示书的数量。第二行为 n 个整数,表示 1∼n 的一种任意排列。同行数之间用空格隔开。
输出格式
每组数据输出一个最少操作次数。如果最少操作次数大于或等于 5 次,则输出 5 or more。每个结果占一行。
数据范围
1 ≤ n ≤ 15
输入样例:
3
6
1 3 4 6 2 5
5
5 4 3 2 1
10
6 8 5 3 4 7 2 9 1 10
输出样例:
2
3
5 or more
来源:https://www.acwing.com/problem/content/description/182/
2. 思路分析:
这道题目如果直接使用暴力来枚举肯定会超时,详细的证明可以参照y总的相关证明,对于dfs搜索的问题一般可以使用迭代加深或者是IDA*算法进行优化,IDA*算法本质上是一个迭代加深加上一个剪枝,在迭代加深的过程中一般需要定义最大深度maxdepth,在dfs搜索的时候在最大深度范围内搜索,如果找到答案那么直接返回,没有找到答案那么扩大搜索范围,也即最大深度要加1,IDA*算法中需要一个启发函数,对于当前长度为i的这一段插入到其他的空位那么最多可以修正3个后继,所以我们可以统计一下当前状态有多少个不正确的后继,那么计算当前状态转移到目标状态至少需要的步数:tot / 3向上取整,相当于是(tot + 2)/ 3,tot为不正确后继的个数,如果当前状态无论如何都会超过maxdepth那么直接return,也即提前剪枝。
3. 代码如下:
from typing import List
class Solution:
# IDA*的启发函数
def f(self, q: List[int]):
count = 0
# 计算不正确的后继个数
for i in range(len(q) - 1):
if q[i + 1] != q[i] + 1: count += 1
# count / 3上取整相当于是(count + k - 1) / k, k = 3
return (count + 2) // 3
# 判断所有后继是否正确
def check(self, q: List[int]):
for i in range(len(q) - 1):
if q[i + 1] != q[i] + 1: return False
return True
# 这里需要注意一个坑就是在回溯的时候需要写成a[:] = t[:], 不能够写成a = t[:]
def dfs(self, depth: int, maxdepth: int, n: int, q: List[int]):
# 利用启发函数剪枝, 预判当前递归下去是否无论如何都无法得到最优解
if self.f(q) + depth > maxdepth: return False
# 判断所有后继是否正确
if self.check(q): return True
# 保存q的副本
t = q[:]
# 枚举交换区间的长度
for _len in range(1, n):
# l为左端点, r为右端点
l = 0
while l + _len - 1 < n:
r = l + _len - 1
for k in range(r + 1, n):
y = l
# 下面交换两段
for x in range(r + 1, k + 1):
q[y] = t[x]
y += 1
for x in range(l, r + 1):
q[y] = t[x]
y += 1
# 如果找到了答案那么直接返回
if self.dfs(depth + 1, maxdepth, n, q): return True
# 回溯, 恢复现场
q[:] = t[:]
l += 1
return False
def process(self):
T = int(input())
for i in range(T):
n = int(input())
q = list(map(int, input().split()))
depth = 0
# 迭代加深, IDA*本质上是在迭代加深的基础上加上了启发函数进行最优性剪枝
while depth < 5 and not self.dfs(0, depth, n, q):
depth += 1
if depth >= 5:
print("5 or more")
else:
print(depth)
if __name__ == '__main__':
Solution().process()