题目
给定一个1~n的排列x,每次你可以将x1~xi翻转。你需要求出将序列变为升序的最小操作次数。
数据范围
n≤25。
题解
对于n≤8的情况,完全可以双向广搜。考察hash和bfs的基本功。
左边的大神一看数据范围就知道是搜索题。而我当时想到的也是搜索,但是剪枝没想到,所以觉得搜索不能过。
然而事实并不是这样。
由于步数最多为
2n
,所以步数很小(解的深度较浅),可以考虑迭代加深。
有东西变了:一些数原来的位置。
有东西没变:靠后的数位置不变。
思维拓展:|ai-aj|>1的对数可能变了,且操作一次对数最多-1,而最后序列的|ai-aj|>1的对数为0。所以得出一个剪枝:当前步数+|ai-aj|>1的对数如果大于期望步数那么就是不可能的。
为什么这个剪枝这么强大?
如果答案越深,那么拓展的情况相对于深度小的状态多得多,那么被剪枝的可能性越大。
总结
对于搜索题:
①什么东西在变,什么东西没有变,大胆地把他们找出来,这样方便剪枝。
②觉得它是搜索,不要觉得暂时没有什么剪枝就放弃,一定会有的,只是没有发现而已。
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define N 30
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
int i,j,ds,n,T,ans;
int a[N];
bool ia;
bool pd(){
int i;
fo(i,1,n)if(a[i]!=i)return 0;
return 1;
}
void rev(int x){
int i;
fo(i,1,x>>1)swap(a[i],a[x-i+1]);
}
void dg(int x,int y){
if(x+y>ans)return;
int i,j;
if(pd()){
ia=1;
return;
}
if(ia)return;
fo(i,2,n){
j=y+(abs(a[i+1]-a[i])==1)-(abs(a[i+1]-a[1])==1);
rev(i);
dg(x+1,j);
if(ia)return;
rev(i);
}
}
int main(){
scanf("%d",&T);
while(T--){
scanf("%d",&n);
fo(i,1,n)scanf("%d",&a[i]);
ds=0;
fo(i,1,n-1)if(abs(a[i]-a[i+1])>1)ds++;
a[n+1]=-1;
fo(ans,0,2*n){
ia=0;
dg(0,ds);
if(ia){
printf("%d\n",ans);
break;
}
}
}
return 0;
}