【SCOI2005】骑士精神(IDA_,A_)

我们先考虑最纯粹的暴力,也就是暴力枚举每次空格调到哪里,并继续递归求解。

然后发现 \(O(8^{15}\times5\times5)\) 的复杂度限制了我们的想象。同学写了一发好像10分

然后既然找不到其它的太好的新算法 我没说剪枝不能过,我们就考虑如何优化暴力。

首先,我们考虑用\(IDA^*\)(启发式迭代加深)来优化这个暴力。

它应该是 \(A^*\) 的进阶版。

  1. 我们对于现在递归到的棋盘的某种状态,计算它的估价函数 \(value()\),也就是从当前棋盘状态变为目标棋盘状态的估计步数

    当然,如果你要保证找到最优解,这个 \(value()\) 必须小于等于真实的步数,也就是说:如果你的估价函数返回值为\(val\),那么你要保证从当前棋盘状态变为目标棋盘状态的真实最优步数至少为 \(val\)

    而且,你这个估价函数 \(value()\) 在上面的条件下,还要尽量大,因为如果估价函数太小,那就和朴素的暴力差不多。

    所以,对于本题的估价函数 \(value()\),我们估计最优解是什么。那么显然,如果当前棋盘现在有 \(x\) 个白棋不在白棋的位置上,有 \(y\) 个黑棋不在黑棋的位置上,由于我们每次操作是交换空格和某个棋子,所以至少需要 \(x+y\) 步才能变为目标棋盘,所以我们把估价函数的返回值设为 \(x+y\) 。但显然,由于空格能跳跃的位置有限制,真实的最优解可能会大于我们估计的这个最优解。

    不知道有没有更好的估价函数,但是这种方法能过我就懒得想了

  2. 我们可以考虑对原来的暴力限制最大递归步数 \(maxdep\) ,那么当你的步数超过了 \(maxdep\) 或者你当前的步数加上估价函数 \(value()\) 的估价步数超过了 \(maxdep\),我们就\(return\)(因为估价步数肯定小于等于真实步数)。

    然后我们从 \(1\)\(15\) 枚举 \(maxdep\),每次进行递归。

    为什么能更快?因为可能的确有一种方法能在\(14\)步变为目标棋盘,但还有一种方法\(2\)步就可以变为目标棋盘了。而由于暴力时遍历顺序的原因,我们可能很晚才会遍历到\(2\)步的方案。所以我们考虑枚举最大步数,进行优化。

所以最后的完整代码如下:

#include<bits/stdc++.h>

using namespace std;

int t,stx,sty,a[5][5],ans[5][5]={{1,1,1,1,1},{0,1,1,1,1},{0,0,2,1,1},{0,0,0,0,1},{0,0,0,0,0}};
int fx[]={-2,-1,1,2,2,1,-1,-2},fy[]={1,2,2,1,-1,-2,-2,-1};

int value()//估价函数
{
	int cnt=0;
	for(int i=0;i<5;i++)
		for(int j=0;j<5;j++)
			if(a[i][j]!=ans[i][j])
				cnt++;
	return cnt;
}

bool Astar(int dep,int x,int y,int maxdep)
{
	if(dep==maxdep)
	{
		if(!value()) return true;
		return false;
	}
	for(int i=0;i<8;i++)
	{
		int xx=x+fx[i],yy=y+fy[i];
		if(xx<0||xx>4||yy<0||yy>4) continue;
		swap(a[x][y],a[xx][yy]);
		int val=value();
		if(dep+val<=maxdep)
			if(Astar(dep+1,xx,yy,maxdep))//如果找到方案就直接返回,这样会省去一些时间
				return true;
		swap(a[x][y],a[xx][yy]);
	}
	return false;
}

int main()
{
	scanf("%d",&t);
	while(t--)
	{
		for(int i=0;i<5;i++)
		{
			for(int j=0;j<5;j++)
			{
				char ch=getchar();
				while(ch!='0'&&ch!='1'&&ch!='*')ch=getchar();
				if(ch=='*')
				{
					a[i][j]=2;
					stx=i,sty=j;
				}
				else a[i][j]=ch-'0';
			}
		}
		for(int maxdep=0;maxdep<=15;maxdep++)//枚举maxdep
		{
			if(Astar(0,stx,sty,maxdep))
			{
				printf("%d\n",maxdep);
				goto end;
			}
		}
		puts("-1");
		end:;
	}
	return 0;
}

最后来自蒟蒻的一个小小的想法(疑问):

既然 \(maxdep=3\) 的情况我们在 \(maxdep=4\) 的时候都会遍历到,但是我们在 \(maxdep=3\) 的时候又遍历过了,那么我们可不可以通过某种方式剪掉这些部分呢?(其实也优化不了多少,顶多把原来的 \(O(k)\) 优化成了 \(O(\frac{k}{25})\)\(k\) 是多少我也不知道,毕竟 \(A^*\)的时间复杂度本来就很玄学)

posted @ 2022-10-29 11:23  ez_lcw  阅读(43)  评论(0编辑  收藏  举报