【BZOJ 1085】[SCOI 2005] 骑士精神(IDA*搜索,双向BFS)

3 篇文章 0 订阅
2 篇文章 0 订阅

题目

Description

在一个 5 × 5 5 \times 5 5×5 的棋盘上有 12 12 12 个白色的骑士和 12 12 12 个黑色的骑士, 且有一个空位。在任何时候一个骑士都能按照骑 士的走法(它可以走到和它横坐标相差为 1 1 1 ,纵坐标相差为 2 2 2 或者横坐标相差为 2 2 2 ,纵坐标相差为 1 1 1 的格子)移动到空位上。 给定一个初始的棋盘,怎样才能经过移动变成如下目标棋盘: 为了体现出骑士精神,他们必须以最少的步数完成任务。

Input

第一行有一个正整数 T T T ( T < = 10 ) (T<=10) (T<=10),表示一共有 N N N 组数据。接下来有 T T T 5 × 5 5 \times 5 5×5 的矩阵, 0 0 0 表示白色骑士, 1 1 1 表示黑色骑士, ∗ * 表示空位。两组数据之间没有空行。

Output

对于每组数据都输出一行。如果能在 15 15 15 步以内(包括 15 15 15 步)到达目标状态,则输出步数,否则输出 − 1 -1 1

Sample Input

2
10110
01*11
10111
01001
00000
01011
110*1
01110
01010
00100

Sample Output

7
-1

原题传送门

思路

  • 首先,最多 15 15 15步所以肯定是暴搜。
  • 纯爆搜肯定是超时的 (你觉得一道省选题的出题人能这么良心?)
  • 首先,这题肯定不能按马走,分支太多了。(虽然我一开始并没有想到)
  • 于是我们要考虑让空格走。
  • 可接着再怎么优化呢?
  • 我们这里有两种办法。
  • 1、同时限制了步数和终态,首先我们能想到双向bfs。
  • 2、如果我们想用dfs的话,就要借助IDA*的帮助。

1、双向bfs

双向bfs,一个从头搜,一个从尾搜,同时进行,可以大大减少时间复杂度。

看到起始状态,目标状态已经给出,我们就能轻松想到用双向bfs。
主要难点就在于状态的记录。
如何记录一个棋盘的状态呢?
——Hash

首先我们先把这个矩阵转换成数字矩阵, 0 0 0 1 1 1 不变, ∗ * 变成 2 2 2
然后再想办法把数字矩阵存下来。
那么如何把这个矩阵存下来呢? (我也不会呀)

  • 把这个矩阵转成一个 25 25 25位数。
  • 数字未免有点太大了。
  • 难道必须要用字符串了吗? (我觉得可以)
  • 于是我们想到状态压缩( 3 3 3进制)。

我们发现,矩阵里的数字无非就 3 3 3 个: 0 , 1 , 2 0,1,2 012
很容易想到用三进制存储。

long long getHash(int (*x)[20]) {
	long long t = 1, ret = 0;
	for (int i = 1; i <= 5; i++)
		for (int j = 1; j <= 5; j++)
			ret += x[i][j] * (t *= 3);
	return ret;
}

最大值也就 3 25 3^{25} 325

其中, 3 25 = 847288609443 ‬ 3^{25}=847288609443‬ 325=847288609443 (这个东西并没有爆long long)。
我们开一个这麽大的数组来存是绝对不现实的。
于是,map悄悄地走了过来,轻而易举的完成了这项任务。
用两个 m a p map map,一个记录从头开始的状态,另一个记录从尾开始的状态。
快活啊 😉。

2、IDA*搜索

  • I D A ∗ IDA* IDA 算法就是基于迭代加深的A*算法。
  • 由于改成了深度优先的方式,与 A ∗ A* A 比起来, I D A ∗ IDA* IDA 更实用:
  • 不需要判重,不需要排序。
  • 空间需求减少。
IDDFS

IDDFS就是迭代加深搜索
既然这个算法的名字叫做迭代加深搜索,那么它的核心在于“迭代加深”了。
那么什么是迭代加深呢?

我们知道,dfs有一种剪枝,当当前局面已经超出了我们所已经搜过的较优解时,当前局面一定不是最优解,想都不用想,直接 return
有的时候,我们搜索已经搜了很深了,仍找不到一个较优解,那就很慢了。
(没事,能骗到分就行。)

我们可以给搜索设置一个约束,当深度已经达到约束却还没有一个答案的时候就 return
如果当前约束并无法找到答案,我们就将约束深度调整的更深一点,再次搜索,直至搜到答案。

A*

A*算法,又称启发式搜索
A*算法比较核心的地方,就在于估价函数了。
定义: f ( n ) = g ( n ) + h ( n ) f(n)=g(n)+h(n) f(n)=g(n)+h(n)
其中 f ( n ) f(n) f(n)是节点的估价函数, g ( n ) g(n) g(n)为从实际代价, h ( n ) h(n) h(n)是对往后状态的最完美估价。


想过这道题,我们就需要把这两种搜索结合在一起,用IDA*解决此题。
我们现在需要做的就是想一个比较优秀的估价函数。
注意:若估价函数不好的话,优化效率就很低,和爆搜没什么区别
(其实能得到分就不错)
我们每次将空白格子和棋子交换,最优的情况就是每次都把棋子移动到目标格子。
那么估价函数就出来了:

int goal[6][6] = {
    {0, 0, 0, 0, 0, 0},
    {0, 1, 1, 1, 1, 1},
    {0, 0, 1, 1, 1, 1},
    {0, 0, 0, 2, 1, 1},
    {0, 0, 0, 0, 0, 1},
    {0, 0, 0, 0, 0, 0},
};
int calc() {
    int ret = 0;
    for (int i = 1; i <= 5; i++)
        for (int j = 1; j <= 5; j++)
            if (a[i][j] != goal[i][j]) ret++;
    return ret;
}

然后在搜索时加上一句:

if (now-1+calc() > sum) return;//最优性剪枝

最后再配上迭代加深搜索:

for (int i = 1; i <= 15; i++) {
	flg = 0, ans = 1292371547;//请暂时忽略flg
	A_star_search(0, x, y, -1);
    if (ans == i) break;
}

再次快活 😃

代码

双向bfs

//1
#include <bits/stdc++.h>

using namespace std;
struct node {
	int a[20][20], x, y;
	bool st_ed;
} fir, goal;

const int dx[10] = {0, -2, -1, 1, 2, 2, 1, -1, -2};
const int dy[10] = {0, 1, 2, 2, 1, -1, -2, -2, -1};

struct nfm{
	bool vis;
	int step;
}; map <long long, nfm> b[2];

long long getHash(int (*x)[20]) {
	long long t = 1, ret = 0;
	for (int i = 1; i <= 5; i++)
		for (int j = 1; j <= 5; j++)
			ret += x[i][j] * (t *= 3);
	return ret;
}

int bfs() {
	queue <node> q;
	for (int i = 0; i < 2; i++) b[i].clear();
	q.push(fir); q.push(goal);
	b[0][getHash(fir.a)].vis = 1;
	b[1][getHash(goal.a)].vis = 1;
	while (!q.empty()) {
		node f = q.front(); q.pop();
		long long tmp = getHash(f.a);
		int lx = f.x, ly = f.y;
		if (b[!f.st_ed][tmp].vis == 1) return b[0][tmp].step+b[1][tmp].step;
		for (int i = 1; i <= 8; i++) {
			int nx = lx+dx[i], ny = ly+dy[i];
			if (nx >= 1 && nx <= 5 && ny >= 1 && ny <= 5) {
				swap(f.a[nx][ny], f.a[lx][ly]);
				f.x = nx, f.y = ny;
				long long tmp1 = getHash(f.a);
				if (b[f.st_ed][tmp1].vis == 0) {
					b[f.st_ed][tmp1].vis = 1;
					b[f.st_ed][tmp1].step = b[f.st_ed][tmp].step+1;
					if (b[f.st_ed][tmp1].step < 8) q.push(f);
				}
				swap(f.a[nx][ny], f.a[lx][ly]);
				f.x = lx, f.y = ly;
			}
		}
	}
	return -1;
}

int main() {
	int T; scanf("%d", &T);
	for (int i = 1; i <= 3; i++) {
		for (int j = 1; j <= i-1; j++) goal.a[i][j] = 0;
		for (int j = i; j <= 5; j++) goal.a[i][j] = 1;
	}
	for (int i = 4; i <= 5; i++) {
		for (int j = 1; j <= i; j++) goal.a[i][j] = 0;
		for (int j = i+1; j <= 5; j++) goal.a[i][j] = 1;
	}
	goal.a[3][3] = 2; goal.x = goal.y = 3; goal.st_ed = 1;
	while (T--) {
		for (int i = 1; i <= 5; i++) {
			char c[110];
			scanf("%s", c);
			for (int j = 0; j < 5; j++)
				if (c[j] == '*') fir.x = i, fir.y = j+1, fir.a[i][j+1] = 2;
				else if (c[j] == '1') fir.a[i][j+1] = 1;
				else fir.a[i][j+1] = 0;
		}
		fir.st_ed = 0;
		int ans = bfs();
		if (ans > 15) puts("-1");
		else printf ("%d\n", ans);
	}
	return 0;
}

IDA*搜索

//2
#include <bits/stdc++.h>

using namespace std;
int goal[6][6] = {
    {0, 0, 0, 0, 0, 0},
    {0, 1, 1, 1, 1, 1},
    {0, 0, 1, 1, 1, 1},
    {0, 0, 0, 2, 1, 1},
    {0, 0, 0, 0, 0, 1},
    {0, 0, 0, 0, 0, 0},
};
const int dx[10] = {0, -2, -1, 1, 2, 2, 1, -1, -2};
const int dy[10] = {0, 1, 2, 2, 1, -1, -2, -2, -1};
int sx, sy, a[10][10], ans;
bool flg;
int calc() {
    int ret = 0;
    for (int i = 1; i <= 5; i++)
        for (int j = 1; j <= 5; j++)
            if (a[i][j] != goal[i][j]) ret++;
    return ret;
}

bool okJump(int x, int y) {
	return x >= 1 && x <= 5 && y >= 1 && y <= 5;
}

void A_star_search(int now, int x, int y, int k) {
	if (flg) return ;
	if (now == k) {
		if (calc() == 0) flg = 1, ans = k;
		return ;
	}
	if (now-1+calc() > k) return ;
	for (int i = 1; i <= 8; i++) {
		int nx = x+dx[i], ny = y+dy[i];
		if (okJump(nx, ny)) {
			swap(a[x][y], a[nx][ny]);
			A_star_search(now+1, nx, ny, k);
			swap(a[x][y], a[nx][ny]);
		}
	}
	return ;
}


int main() {
	int T; scanf("%d", &T);
	while (T--) {
		int sx, sy;
		for (int i = 1; i <= 5; i++) {
			char c[110];
			scanf("%s", c);
			for (int j = 0; j < 5; j++)
				if (c[j] == '*') a[i][j+1] = 2, sx = i, sy = j+1;
				else if (c[j] == '1') a[i][j+1] = 1;
				else a[i][j+1] = 0;
		}
		for (int i = 0; i <= 15; i++) {
			flg = 0; ans = 1292371547;
			A_star_search(0, sx, sy, i);
			if (ans == i) break;
		}
		if (ans <= 15) printf("%d\n", ans);
		else puts("-1");
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值