POJ - 1077 简单做法 A*

2 篇文章 0 订阅

一、内容

在一个3×3的网格中,1~8这8个数字和一个“X”恰好不重不漏地分布在这3×3的网格中。

例如:

1 2 3
X 4 6
7 5 8

在游戏过程中,可以把“X”与其上、下、左、右四个方向之一的数字交换(如果存在)。

我们的目的是通过交换,使得网格变为如下排列(称为正确排列):

1 2 3
4 5 6
7 8 X

例如,示例中图形就可以通过让“X”先后与右、下、右三个方向的数字交换成功得到正确排列。

交换过程如下:

1 2 3 1 2 3 1 2 3 1 2 3
X 4 6 4 X 6 4 5 6 4 5 6
7 5 8 7 5 8 7 X 8 7 8 X

把“X”与上下左右方向数字交换的行动记录为“u”、“d”、“l”、“r”。

现在,给你一个初始网格,请你通过最少的移动次数,得到正确排列。

二、思路

  • 不想去写康拓展开,那就只能用unordered_map,但是POJ又不支持,那就只能用速度慢点的map来映射了。
  • h函数估价:全部的点与自己原来位置的曼哈顿距离之和
  • 记录路径,记录上一次的状态和本次的状态,最后反向输出一下就行了。
  • 然后就是如何判断有无解, 当我们将所有数字横向展开(x除外),求出逆序对的个数,如果逆序对为奇数那么就是无解。

三、代码

#include <cstdio>
#include <iostream>
#include <cstring> 
#include <algorithm>
#include <cmath>
#include <queue>
#include <map>  //换成unordered_map会快很多 但是POJ不支持 
using namespace std;
int  x, y; 
int dx[4] = {-1, 1, 0, 0};
int dy[4] = {0, 0, -1, 1}; 
struct node{ 
	int f;
	string str;
	node(int f, string str): f(f), str(str){}
	friend bool operator < (node a, node b) {
		return a.f > b.f; //转化为小根堆 
	}
};
// 0上 1下 2左 3右 
char path[4] = {'u', 'd', 'l', 'r'};
int getH(string str) {
	int h = 0;
	for (int i = 0; i < 9; i++) {
		//求出曼哈顿距离
		if (str[i] != 'x') {
			//当做01234567x  这样坐标是(t / 3, t % 3) 
			int t = str[i] - '1';
			//当前字符的坐标(i / 3, i % 3) 正确的位置(t / 3, t % 3)
			h += abs(i / 3 - t / 3) + abs(i % 3 - t % 3); 
		}
	}
	return h;
} 

string bfs(string start){
	string last, next;
	string end = "12345678x";
	//代表移动过的步数 有点像djkstra的d数组的作用  但是这里初始是0所以第一判断应该判断是否为0 以后再判断大小  
	map<string, int> g;  
	map<string, bool> vis; //看是否访问过该状态
	map<string, pair< int, string> > pre; 
	priority_queue<node> q;
	g[start] = 0; 
	q.push(node(getH(start), start));
	int step; //记录移动的步数 
	while (q.size()) {
		node t = q.top();
		q.pop();
		last = t.str;
		if (vis[last]) continue; 
		vis[last]++;
		step = g[last]; 
		//到达目标状态退出循环 
		if (last == end) break;
		//查找x的坐标 
		for (int i = 0; i < last.size(); i++) {
			if (last[i] == 'x') {
				x = i / 3;
				y = i % 3; 
				break;
			}
		} 
		//枚举4个方向
		next = last;
		for (int i = 0; i < 4; i++) {
			int fx = dx[i] + x;
			int fy = dy[i] + y;	
			if (fx < 0 || fy < 0 || fx >= 3 || fy >= 3) continue;
			swap(next[x *3 + y], next[fx * 3 + fy]);
			//判断这个状态能否入队 没有访问过直接入队 或者 步数比较少入队 
			//if (!g.count(next) || g[next] > step + 1) {
			if (!vis[next]) {  //未访问过的状态肯定是直接入队访问过的步数必然会多1  
				g[next] = step + 1;
				q.push(node(step + 1 + getH(next), next));
				//记录路径 保存上一状态 
				pre[next] = make_pair(i, last);
			} 
			//将next换回去 
			swap(next[x *3 + y], next[fx * 3 + fy]); 
		}  
	} 
	next = "";
	while (end != start) {
		next += path[pre[end].first];
		end = pre[end].second;
	}
	reverse(next.begin(), next.end());
	return next;
}

int main() {
	string start, last, next;
	for (int i = 1; i <= 9; i++) {
		cin >> next;
		start += next;
		if (next != "x") last += next;
	}
	//求逆序对的数量 判断是否有解
	int cnt = 0;
	for (int i = 0; i < 7; i++) {
		for (int j = i + 1; j < 8; j++) {
			if (last[j] < last[i]) cnt++;
		}
	}
	if (cnt % 2 != 0) {
		cout << "unsolvable" << endl;
	} else {
		cout <<	bfs(start) << endl;
	}
	return 0; 
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
这是一道比较经典的计数问题。题目描述如下: 给定一个 $n \times n$ 的网格图,其中一些格子被标记为障碍。一个连通块是指一些被标记为障碍的格子的集合,满足这些格子在网格图中连通。一个格子是连通的当且仅当它与另一个被标记为障碍的格子在网格图中有公共边。 现在,你需要计算在这个网格图中,有多少个不同的连通块,满足这个连通块的大小(即包含的格子数)恰好为 $k$。 这是一道比较经典的计数问题,一般可以通过计算生成函数的方法来解决。具体来说,我们可以定义一个生成函数 $F(x)$,其中 $[x^k]F(x)$ 表示大小为 $k$ 的连通块的个数。那么,我们可以考虑如何计算这个生成函数。 对于一个大小为 $k$ 的连通块,我们可以考虑它的形状。具体来说,我们可以考虑以该连通块的最左边、最上边的格子为起点,从上到下、从左到右遍历该连通块,把每个格子在该连通块中的相对位置记录下来。由于该连通块的大小为 $k$,因此这些相对位置一定是 $(x,y) \in [0,n-1]^2$ 中的 $k$ 个不同点。 现在,我们需要考虑如何计算这些点对应的连通块是否合法。具体来说,我们可以考虑从左到右、从上到下依次处理这些点,对于每个点 $(x,y)$,我们需要考虑它是否能够与左边的点和上边的点连通。具体来说,如果 $(x-1,y)$ 和 $(x,y)$ 都在该连通块中且它们在网格图中有公共边,那么它们就是连通的;同样,如果 $(x,y-1)$ 和 $(x,y)$ 都在该连通块中且它们在网格图中有公共边,那么它们也是连通的。如果 $(x,y)$ 与左边和上边的点都不连通,那么说明这个点不属于该连通块。 考虑到每个点最多只有两个方向需要检查,因此时间复杂度为 $O(n^2 k)$。不过,我们可以使用类似于矩阵乘法的思想,将这个过程优化到 $O(k^3)$ 的时间复杂度。 具体来说,我们可以设 $f_{i,j,k}$ 表示状态 $(i,j)$ 所代表的点在连通块中,且连通块的大小为 $k$ 的方案数。显然,对于一个合法的 $(i,j,k)$,我们可以考虑 $(i-1,j,k-1)$ 和 $(i,j-1,k-1)$ 这两个状态,然后把点 $(i,j)$ 加入到它们所代表的连通块中。因此,我们可以设计一个 $O(k^3)$ 的 DP 状态转移,计算 $f_{i,j,k}$。 具体来说,我们可以考虑枚举连通块所包含的最右边和最下边的格子的坐标 $(x,y)$,然后计算 $f_{x,y,k}$。对于一个合法的 $(x,y,k)$,我们可以考虑将 $(x,y)$ 所代表的点加入到 $(x-1,y,k-1)$ 和 $(x,y-1,k-1)$ 所代表的连通块中。不过,这里需要注意一个细节:如果 $(x-1,y)$ 和 $(x,y)$ 在网格图中没有相邻边,那么它们不能算作连通的。因此,我们需要特判这个情况。 最终,$f_{n,n,k}$ 就是大小为 $k$ 的连通块的个数,时间复杂度为 $O(n^2 k + k^3)$。 参考代码:

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值