链接
题意
给出一组1到n(n < 10)的排列和一种操作“剪切粘贴”,可以选定排列的一段剪掉并且粘贴在任意其他位置形成新的排列,问最少执行几次操作可以得到排列1, 2, … n。
题解
将一个排列看做一个状态就是标准的宽搜题,9!并不大,但是后继状态特别多,宽搜肯定是妥妥地超时。
迭代加深搜索,如刘汝佳所说是一个“长期以来被低估的算法”,它由于限定了深度maxd,给搜索过程提供了剪枝的可能,效率上大大优化了。
代码
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <vector>
#include <queue>
using namespace std;
int n, maxd, v[10][10];
inline int badx(const int s[]) {
int ret = 0; for(int i = 0; i < n-1; i++) ret += int(s[i + 1] != s[i] + 1); return ret;
}
bool IDAstar(int cur)
{
if(cur == maxd) {
for(int i = 0; i < n-1; i++) if(v[cur][i] + 1 != v[cur][i + 1]) return false; return true;
}
if(badx(v[cur]) - 3 * (maxd - cur) > 0) return false;
for(int l = 0; l < n; l++) {
for(int r = l; r < n; r++) {
if(r - l + 1 == n) break;
if(l > 0 && v[cur][l-1] + 1 == v[cur][l] || r < n-1 && v[cur][r] + 1 == v[cur][r+1]) continue;
for(int k = 0; k < n; k++) if(k < l || k > r + 1) {
for(int i = 0, m = 0; i < n; i++) {
if(i >= l && i <= r) continue;
if(i == k) for(int j = l; j <= r; j++) v[cur + 1][m++] = v[cur][j];
v[cur + 1][m++] = v[cur][i];
}
if(IDAstar(cur + 1)) return true;
}
}
}
return false;
}
int main()
{
//freopen("in.txt", "r", stdin); freopen("out.txt", "w", stdout);
int kase = 0;
while(cin >> n) { if(0 == n) break;
for(int i = 0; i < n; i++) scanf("%d", &v[0][i]);
printf("Case %d: ", ++kase);
bool ok = true; for(int i = 0; i < n-1; i++)
if(v[0][i] + 1 != v[0][i+1]) { ok = false; break; }
if(ok) { printf("0\n"); continue; }
for(maxd = 1; maxd < n; maxd++)
if(IDAstar(0)) { printf("%d\n", maxd); break; }
}
return 0;
}