JZOJ5441. 【NOIP2017提高A组冲刺11.1】序列 启发式搜索+迭代深搜

时间:2022-09-11 20:49:45

题意:给定一个1~n的排列x,每次你可以将x1~xi翻转。你需要求出将序列变为升序的最小操作次数。有多组数据。
n<=25
吃了搜索的亏,表示估价函数这玩意儿碰都没碰过,A*也是好久以前才做过的。。考试的时候打了个搜索还错了,没脸见人了。。
有两个优化。
第一个就是估价函数,这个必须加上,每次交换的时候,我们假设当前已经交换了x步,然后枚举答案,估价函数g为接下来要把当前序列变为升序的期望步数,他的映射为序列内相邻差>1的个数,那么x+g>ans则跳出。
第二个优化也加上就可以在100ms内跑完,就是我们不传递估价函数,而是记录当前做到哪一位,设为y,那么y+1…n已经放置完了,那么每一次我们暴力计算估价函数,然后也可以做到第一个剪枝,第二个剪枝就是当y=0是可以跳出,其实这个也不用特意这么写,但是我发现比起在dfs内暴力计算,好像传递的时间复杂度更高一些。
代码(跑的飞起)

#include<cstdio>
#include<algorithm>
#include<cstring>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
using namespace std;
const int N=35;
int n,m;
int a[N],d[N],ans;
bool flag;
inline void rev(int x)
{
fo(i,1,x/2)
swap(a[i],a[x-i+1]);

}
inline int getlim()
{
fo(i,1,n)a[i]=d[i];
int ans=0;
fd(i,n,1)
{
int k;
fo(j,1,i)
if (a[j]==i)
{
k=j;
break;
}
if (k==i)continue;
if (k>1)rev(k),ans++;
rev(i),ans++;
}
return ans;
}
inline void dfs(int x,int y)
{
if (x>ans)return;
if (flag)return;
while (a[y]==y&&y>0)y--;
if (!y)
{
flag=1;
return;
}
int g=0;
fo(i,2,y+1)if (abs(a[i]-a[i-1])>1)g++;
if (x+g>ans)return;
fo(i,2,y)
{
rev(i);
dfs(x+1,y);
rev(i);
}
}
int main()
{
//freopen("sequence.in","r",stdin);
//freopen("sequence.out","w",stdout);
int cas;
scanf("%d",&cas);
while (cas--)
{
scanf("%d",&n);
ans=0;
fo(i,1,n)
{
scanf("%d",&a[i]);
}
int y=n;
while (a[y]==y&&y>0)y--;
for(ans=0;ans<=2*n-2;ans++)
{
flag=0;dfs(0,y);
if (flag)break;
}
printf("%d\n",ans);
}
}