给你一个长度为n(n<=9)的序列,每次可以将一段连续的子序列剪切到其他地方,问最少多少次操作能将序列变成升序。
本题最大的坑点在于让人很容易想到许多感觉挺正确但实际却不正确的策略来避开一些看似“不好”的操作,比如:不破坏连续的片段,拼接时总是保证多出一个比它左边的数大一的数,等等。而实际上这些策略都是错误的,所以只能暴搜。“将一段连续的子序列剪切到其他地方”等价于“交换两段连续的片段”,因此可以枚举三个划分点i,j,k进行搜索,但这样的话dfs树会很宽大,会TLE。考虑到最少只需要n-1步就能完成任务,因此可以用IDA*来优化,设估价函数h代表当前序列中不正确的数的个数,则每次操作最多将h减少3,用这个来剪枝。
“交换片段”的操作的分界点的选取是个难点,如果自己边界处理的能力不够好的话,不断调参去试也不失为一个好的方法(人体模拟退火)。此外,也可以用C++的库函数copy来简化代码。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=+;
int a[N],b[N],n,ka;
int calh() {
int ret=;
for(int i=; i<n-; ++i)if(a[i]+!=a[i+])ret++;
if(a[n-]!=n)ret++;
return ret;
} void cut(int l,int m,int r) {
copy(a+m,a+r,b);
copy(a+l,a+m,b+(r-m));
copy(b,b+(r-l),a+l);
} bool dfs(int dep,int mxd) {
int h=calh();
if(h==)return ;
if(*dep+h>*mxd)return ;
for(int i=; i<n; ++i)
for(int k=i+; k<=n; ++k)
for(int j=i+; j<k; ++j) {
cut(i,j,k);
if(dfs(dep+,mxd))return ;
cut(i,i+(k-j),k);
}
return ;
} int IDAStar() {for(int mxd=;; ++mxd)if(dfs(,mxd))return mxd;} int main() {
while(scanf("%d",&n)&&n) {
printf("Case %d: ",++ka);
for(int i=; i<n; ++i)scanf("%d",&a[i]);
printf("%d\n",IDAStar());
}
return ;
}