题目描述
给定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
思路
根据题目要求,我们只需要考虑在4次操作以内是否能实现目标,也就是我们只需要考虑搜索树的前4层。本层的搜索树的规模能够达到560^4, 超时。
方案一:采用双向BES.从起始状态、目标状态开始各搜索2步,看能否到达相同的状态进行衔接,复杂度降低为560^2。
方案二:在目标状态下,第i本书后边应该是第i+1本书,我们称i+1是i的正确后继。每次修改最多可以修改3个节点,如果序列中顺序不对的地方有tot个, 那么我们最少可以修改tot / 3 次得到最终结果。所以我们设置估计函数为f(n) = tot / 3 上取整。 我们采用迭代加深的方法,从1~4依次限制搜索深度,然后从起始状态出发DFS。DFS时,在每个状态下直接枚举抽取哪一段、移动到更靠后的哪个位置,沿着该分支深入即可。
代码
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
using namespace std;
const int N = 20;
int q[N];
int stp[5][N];
int n;
int f(){
int ans = 0;
for(int i = 0; i < n - 1; i++){
if(q[i + 1] != q[i] + 1)
ans++;
}
return (ans + 2) /3;
}
bool check(){
for(int i = 0; i < n; i++){
if(q[i] != i + 1)
return false;
}
return true;
}
bool dfs(int depth, int maxx){
if(depth + f() > maxx) return false;
if(check()) return true;
for(int l = 0; l < n; l++)
for(int r = l; r < n; r++){
for(int k = r + 1; k < n; k++){
memcpy(stp[depth], q, sizeof q);
int y, x;
for(x = r + 1, y = l; x <= k; x++, y++) q[y] = stp[depth][x];
for(x = l; x <= r; x++, y++ ) q[y] = stp[depth][x];
if(dfs(depth + 1, maxx)) return true;
memcpy(q, stp[depth], sizeof q);
}
}
return false;
}
int main(){
int t;
cin >> t;
while(t--){
cin >> n;
for(int i = 0; i < n; i++) cin >> q[i];
int depth = 0;
while(depth < 5 && !dfs(0, depth)) depth++;
if(depth >= 5) cout << "5 or more" << endl;
else cout << depth << endl;
}
return 0;
}