题意:你有一篇n (2≤n≤9) 个自然段组成的文章,希望将它们排列成1,2,…,n。可以用Ctrl+X(剪切)和Ctrl+V(粘贴)快捷键来完成任务。每次可以剪切一段连续的自然段,粘贴时按照顺序粘贴。注意,剪贴板只有一个,所以不能连续剪切两次,只能剪切和粘贴交替。例如,为了将{2,4,1,5,3,6}变为升序,可以剪切1将其放到2前,然后剪切3将其放到4前。再如,排列{3,4,5,1,2},只需一次剪切和一次粘贴即可——将{3,4,5}放在{1,2}后,或者将{1,2}放在{3,4,5}前。(本段摘自《算法竞赛入门经典(第2版)》)
分析:
可以使用IDA*来求解,关键在于启发函数。考虑后继不正确的数字个数为h,可以证明每次剪切时h最多减少3,因此当
3∗deep+h<3∗limit
时可以剪枝,其中deep为当前深度,limit为搜索深度限制。
代码:
#include <iostream>
#include <algorithm>
#include <fstream>
#include <cstring>
#include <vector>
#include <queue>
#include <cmath>
#include <cctype>
#include <stack>
#include <set>
using namespace std;
const int maxn = 10 + 5;
int n, C;
int a[maxn];
string ch;
int error(const string& s)
{
int cnt = 0;
for (int i = 0; i < n - 1; ++i)
if (s[i] + 1 != s[i + 1])
++cnt;
if (s[n - 1] != n + '0')
++cnt;
return cnt;
}
bool DFS(string s, int deep, int limit)
{
int err = error(s);
if (err == 0)
return true;
if (deep == limit)
return false;
if (err + 3 * deep > 3 * limit)
return false;
for (int i = 0; i < n; ++i)
for (int j = i; j < n; ++j)
for (int k = 0; k < n; ++k)
if (k < i || k > j)
{
string ss = s;
string tmp = ss.substr(i, j - i + 1);
ss.erase(i, j - i + 1);
if (k < i)
ss.insert(k, tmp);
else
ss.insert(k - (j - i + 1), tmp);
if (DFS(ss, deep + 1, limit))
return true;
}
return false;
}
int main()
{
while (~scanf("%d", &n), n)
{
ch = "";
for (int i = 0; i < n; ++i)
{
scanf("%d", &a[i]);
ch += (char)(a[i] + '0');
}
for (int i = 0; ; ++i)
if (DFS(ch, 0, i))
{
printf("Case %d: %d\n", ++C, i);
break;
}
}
return 0;
}