题目很明显使用搜索算法,普通BFS会导致超时,使用A*算法进行搜索。
计算当前状态的每个数字直接移动到他应该在的位置需要的次数求和,作为A*算法的h函数值。
画了几次找规律发现怎么移动逆序对都为偶数,所以特判当逆序对为奇数时不可行。
在搜索过程中,已经搜索到的状态不再进行搜索,需要使用数组进行标记,由于0~8的全排列直接用2进制或10进制表示过大。
使用康拓展卡进行状态压缩,将0~8的全排列压缩为0~9!个状态。
题目还要求记录操作,使用path类,类似于链式前向星的方式将答案存储在p数组中,最后使用print函数递归输出。
补双向BFS法:
题目也可以使用双向BFS方法求解,以起点和终点同时BFS的方式将复杂度降低。
使用两个队列、vis数组、p路径数组,在题目所给状态和要求的初始状态交替进行双向BFS,当某一次取出队伍节点时发现另一个分支已经访问过这个状态时说明两个BFS相遇,递归进行输出答案。
记录路径时反向的BFS需要反向输出,不能再进行拉链,需要两个vis数组记录当前状态从哪个状态经过什么操作转移。
递归输出时需要分正向和反向,进行先递归后输出和先输出后递归,还要将存储的方向进行翻转(+2模4)。
A*:
#include
#include
#define fst first
#define sed second
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
const ll LINF = 0x3f3f3f3f3f3f3f3f;
const int N = 10;
const int M = 3.7e5;
int fac[N];
bool vis[M];
int dir[4][2] = { -1, 0, 1, 0, 0, -1, 0, 1 }, idx;
char mov[] = "udlr";
int a[N][N], s, z, g, h, m;
struct node
{
int s, z, g, h, m; //状态 空白位置 次数 dis值 路径
bool operator < (const node &oth) const
{
if (h != oth.h) //任意长度答案
return h > oth.h;
return g > oth.g;
}
};
struct path //路径
{
int nxt;
char ch;
}p[M];
int dis(int(*g)[N])
{
int res = 0;
for (int i = 0; i < 3; i++)
for (int j = 0; j < 3; j++)
{
int v = (g[i][j] + 8) % 9;
res += abs(v / 3 - i) + abs(v % 3 - j); //应该在的行列距离差
}
return res;
}
int Contor(int(*g)[N]) //康拓展卡 图转编码
{
int a[N], res = 0;
for (int i = 1; i <= 9; i++)
a[i] = g[(i - 1) / 3][(i - 1) % 3];
for (int i = 1; i <= 9; i++)
{
int cnt = 0;
for (int j = i + 1; j <= 9; j++)
if (a[i] > a[j])
cnt++;
res += cnt * fac[9 - i];
}
return res;
}
void ReContor(int(*g)[N], int s) //编码转图
{
int vis[N] = { 0 };
for (int i = 1; i <= 9; i++)
{
int cnt = s / fac[9 - i], j;
for (j = 0; j < 9; j++)
if (!vis[j])
{
if (!cnt)
break;
cnt--;
}
vis[j] = 1;
g[(i - 1) / 3][(i - 1) % 3] = j;
s %= fac[9 - i];
}
}
void Print(int i) //根据拉链递归输出
{
if (p[i].nxt)
Print(p[i].nxt);
printf("%c", p[i].ch);
}
void AStar()
{
s = Contor(a);
int e = dis(a);
idx = 0;
memset(vis, 0, sizeof(vis));
priority_queue<node> pq;
pq.push({ s, z, 0, 0, 0 }); //状态 空白位置 次数 dis值 操作
while (!pq.empty())
{
node f = pq.top(); pq.pop();
if (f.s == 46233) //46233初始状态码
{
Print(f.m), cout << endl;
return;
}
vis[f.s] = 1;
ReContor(a, f.s); //转为图
int x = f.z / 3, y = f.z % 3;
for (int k = 0; k < 4; k++)
{
int xx = x + dir[k][0], yy = y + dir[k][1];
if (xx >= 0 && xx < 3 && yy >= 0 && yy < 3)
{
swap(a[x][y], a[xx][yy]);
s = Contor(a), z = xx * 3 + yy;
if (!vis[s]) //未出现的状态
{
g = f.g + 1, h = dis(a), m = ++idx;
p[m].ch = mov[k], p[m].nxt = f.m; //操作拉链
pq.push({ s, z, g, h, m }); //新状态
}
swap(a[x][y], a[xx][yy]); //还原
}
}
}
printf("unsolvable\n");
}
int main()
{
#ifdef LOCAL
freopen("C:/input.txt", "r", stdin);
#endif
fac[0] = 1;
for (int i = 1; i <= N; i++)
fac[i] = fac[i - 1] * i;
while (true)
{
char c;
for (int i = 0; i < 3; i++)
for (int j = 0; j < 3; j++)
{
if (scanf(" %c", &c) == EOF)
exit(0);
if (c == 'x') a[i][j] = 0, z = i * 3 + j;
else a[i][j] = c - '0';
}
int rev = 0; //逆序数
for (int i = 0; i < 9; i++)
for (int j = i + 1; j < 9; j++)
if (a[i / 3][i % 3] && a[j / 3][j % 3] &&
a[i / 3][i % 3] > a[j / 3][j % 3])
rev++;
if (rev & 1) //如果逆序对为奇数则不可行
printf("unsolvable\n");
else
AStar();
}
return 0;
}
双向BFS:
#include
#include
#define fst first
#define sed second
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
const ll LINF = 0x3f3f3f3f3f3f3f3f;
const int N = 10;
const int M = 3.7e5;
int fac[N], a[N][N], s, z;
bool vis[2][M];
int dir[4][2] = { -1, 0, 0, -1, 1, 0, 0, 1 };
char mov[] = "uldr";
struct node
{
int s, z, t; //状态 空白位置 次数
};
struct path //路径
{
int nxt, ch;
}p[2][M];
int Contor(int(*g)[N]) //康拓展卡 图转编码
{
int a[N], res = 0;
for (int i = 1; i <= 9; i++)
a[i] = g[(i - 1) / 3][(i - 1) % 3];
for (int i = 1; i <= 9; i++)
{
int cnt = 0;
for (int j = i + 1; j <= 9; j++)
if (a[i] > a[j])
cnt++;
res += cnt * fac[9 - i];
}
return res;
}
void ReContor(int(*g)[N], int s) //编码转图
{
int vis[N] = { 0 };
for (int i = 1; i <= 9; i++)
{
int cnt = s / fac[9 - i], j;
for (j = 0; j < 9; j++)
if (!vis[j])
{
if (!cnt)
break;
cnt--;
}
vis[j] = 1;
g[(i - 1) / 3][(i - 1) % 3] = j;
s %= fac[9 - i];
}
}
void Print(int i, int k) //i指针 k哪个分支
{
if (p[k][i].nxt && !k) //正向先递归
Print(p[k][i].nxt, k), putchar(mov[p[k][i].ch]);
else if (p[k][i].nxt && k)
putchar(mov[(p[k][i].ch + 2) % 4]), Print(p[k][i].nxt, k); //反向注意反转方向
}
void DBFS()
{
s = Contor(a);
memset(vis, 0, sizeof(vis));
queue<node> q[2];
q[0].push({ s, z, 0 }); //状态 空白位置 次数
q[1].push({ 46233, 8, 0 }); //46233初始状态码
p[0][s].nxt = p[0][46233].nxt = 0; //这样就不用清空p数组了
while (!q[0].empty() || !q[1].empty())
for (int i = 0; i < 2; i++) //遍历两个分支
if (!q[i].empty())
{
node f = q[i].front(); q[i].pop();
if (vis[!i][f.s]) //和另一个分支相遇
{
Print(f.s, 0), Print(f.s, 1), cout << endl;
return;
}
vis[i][f.s] = 1;
ReContor(a, f.s); //转为图
int x = f.z / 3, y = f.z % 3;
for (int k = 0; k < 4; k++)
{
int xx = x + dir[k][0], yy = y + dir[k][1];
if (xx >= 0 && xx < 3 && yy >= 0 && yy < 3)
{
swap(a[x][y], a[xx][yy]);
s = Contor(a), z = xx * 3 + yy;
if (!vis[i][s]) //未出现的状态
{
p[i][s].ch = k, p[i][s].nxt = f.s; //记录当前状态由哪个状态转移来和转移的操作
q[i].push({ s, z, f.t + 1 }); //新状态
}
swap(a[x][y], a[xx][yy]); //还原
}
}
}
printf("unsolvable\n");
}
int main()
{
#ifdef LOCAL
freopen("C:/input.txt", "r", stdin);
#endif
fac[0] = 1;
for (int i = 1; i <= N; i++)
fac[i] = fac[i - 1] * i;
while (true)
{
char c;
for (int i = 0; i < 3; i++)
for (int j = 0; j < 3; j++)
{
if (scanf(" %c", &c) == EOF)
exit(0);
if (c == 'x') a[i][j] = 0, z = i * 3 + j;
else a[i][j] = c - '0';
}
int rev = 0; //逆序数
for (int i = 0; i < 9; i++)
for (int j = i + 1; j < 9; j++)
if (a[i / 3][i % 3] && a[j / 3][j % 3] &&
a[i / 3][i % 3] > a[j / 3][j % 3])
rev++;
if (rev & 1) //如果逆序对为奇数则不可行
printf("unsolvable\n");
else
DBFS();
}
return 0;
}