180 排书(IDA*)

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()

Guess you like

Origin blog.csdn.net/qq_39445165/article/details/121845928