题意: 给你一个长度为N(N<=15)的序列,让你通过最少的操作次数将这个序列变成单调递增的。允许的操作为:选择一段连续的区间,将其从原序列中取出,然后将其插入到任意位置。
思路:首先我们可以发现,最多的操作次数为N次,即每次我们只将一个数字放到正确的位置。但是最少的操作次数该如何计算呢?
因为,我们注意到对于不同的序列,其操作的方法没有明显的规律性,这样我们就要用搜索来完成这个问题,同时在前面也提到了操作次数的上界,那IDA*算法就是个很好的选择。
IDA*算法的关键就是设计启发式函数h,即当前状态到最终状态的下界。这里,我们用h来表示后继不正确的数字个数,可以得到性质:每次操作,h的值最多减少3。那么,一个很自然的剪枝就是:3d+h > 3maxd。即,如果对于当前状态的h,剩下的(maxd - d)次操作都不能将其变成0,则剪枝。
代码如下:
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
const int MAX = 20;
int T,N;
int a[20];
int sa[20];
bool judge()
{
for(int i = 0; i < N; ++i)
if(sa[i] != a[i]) return false;
return true;
}
int h()
{
int ret = 0;
for(int i = 0; i < N - 1; ++i)
if(a[i+1] != a[i]+1) ret++;
if(a[N-1] != N) ret++;
return ret;
}
bool dfs(int d, int maxd)
{
if(judge()) return true;
if(3 * d + h() > 3 * maxd) return false;
int olda[20];
int b[20],tot;
memcpy(olda,a,sizeof(a));
for(int i = 0; i < N; ++i){
for(int j = i; j < N; ++j){
tot = 0;
for(int k = i; k <= j; ++k)
b[tot++] = olda[k];
for(int k = 0; k < N; ++k){
memcpy(a,olda,sizeof(olda));
if(k < i){//在k位置的前面插入
for(int u = 0; u < i - k; ++u)
a[j - u] = a[i - u - 1];
for(int u = 0; u < tot; ++u)
a[k + u] = b[u];
if(dfs(d+1,maxd)) return true;
}
else if(k > j){//在k位置的后面插入
for(int u = 0; u < k - j; ++u)
a[i + u] = a[j + u + 1];
for(int u = 0 ; u < tot; ++u)
a[i + k - j + u] = b[u];
if(dfs(d+1,maxd)) return true;
}
}
}
}
return false;
}
int IDAstar()
{
if(judge()) return 0;
for(int i = 1; i <= 4; ++i)
if(dfs(0,i)) return i;
return 5;
}
int main(void)
{
//freopen("input.txt","r",stdin);
scanf("%d", &T);
while(T--){
scanf("%d",&N);
for(int i = 0; i < N; ++i){
scanf("%d",&a[i]);
sa[i] = a[i];
}
sort(sa,sa+N);
int ans = IDAstar();
if(ans <= 4)
printf("%d\n",ans);
else
puts("5 or more");
}
return 0;
}