HDU-1043 Eight(经典八数码问题, A*+康拓+曼哈顿距离+逆序数判断可解性、双向搜索)
题意:
给定一个序列,由1~8数字和字母x组成,表示的是一个3*3的矩形。每次操作x都能与相邻的数字交换,问如何操作才能使得序列为{1,2,3,4,5,6,7,8,x}。
思路:
经典八数码问题,寻找解,肯定是越少的操作越有可解性。所以利用康拓展开将字符串hash成数值之后,很容易就能想出暴力广搜的思路,但是肯定会超时,因为状态共9!个,广搜稍微一扩散就爆炸了。优化搜索都会想用A*,可是估价函数不大好找啊,一个稍简单的估价函数:计算当前状态与目标状态的同一位置不同数字的个数。但是仍然超时。网上大多数代码都用到了曼哈顿距离去估价,其实确实是可行的,这么估价肯定是小于等于真正代价的。但是还不够AC...网上大多数代码又用到了利用这个序列逆序数的奇偶性进行判断游戏能否可解,即当且仅当初始状态逆序对个数与目标状态逆序对个数奇偶性相同才有解。想不大懂orz...(证明链接)
另外还可以用双向搜索做。双向广度优先算法从两个方向以广度优先的顺序同时扩展,一个是从起始节点开始扩展,另一个是从目的节点扩展,直到一个扩展队列中出现另外一个队列中已经扩展的节点,也就相当于两个扩展方向出现了交点,那么此时可以认为我们找到了一条路径。
双向广度优先算法相对于广度优先算法来说,由于采用了从两个根开始扩展的方式,搜索树的深度得到了明显的减少,所以在算法的时间复杂度和空间复杂度上都有较大的优势!双向广度优先算法特别适合于给出了起始节点和目的节点,要求它们之间的最短路径的问题。其正确性是根据:广度优先的顺序能够保证找到的路径就是最短路径!
双向搜索可以缩短很长的时间。例如一个迷宫,我们假设没有边界,没有墙的空地,单BFS需要4^n时间,而双向BFS就是4^(n/2)*2,缩短了很多。
A*代码:
//#include <unordered_map>
#include <algorithm>
#include <iostream>
#include <string.h>
#include <string>
#include <cstdio>
#include <queue>
using namespace std;
const int maxn = 4e5;
int nxt[4][2] = {0, 1, 1, 0, 0, -1, -1, 0};
char pnt[] = "rdlu";
struct node
{
int posx, posy, t, h;
char s[3][3];
bool operator<(const node k)const
{
return t+h > k.t+k.h;
}
} x;
priority_queue<node> q;
//unordered_map<string, int> has;
//用哈希map优化常数竟然比不优化还耗时...
int bas[10];
int ans[maxn], pre[maxn];
char ch, sa[maxn];
bool vis[maxn];
void init()
{
bas[0] = 1;
for(int i = 1; i < 9; ++i)
bas[i] = bas[i-1]*i;
}
int H() //估价函数:计算曼哈顿距离
{
int res = 0;
for(int i = 0; i < 9; ++i)
if(x.s[i/3][i%3] != '8')
res += abs(i/3-(x.s[i/3][i%3]-'0')/3)+abs(i%3-(x.s[i/3][i%3]-'0')%3);
return res;
}
bool isok() //通过逆序数的奇偶性判断游戏可解性
{
int res = 0;
for(int i = 0; i < 9; ++i)
for(int j = i+1; j < 9; ++j)
{
if(x.s[i/3][i%3] != '8' && x.s[j/3][j%3] != '8' &&
x.s[i/3][i%3] > x.s[j/3][j%3])
++res;
}
return !(res&1);
}
int getNo()
{
//string s = "";
//for(int i = 0; i < 9; ++i) s += x.s[i/3][i%3];
//if(has.find(s) != has.end()) return has[s];
int res = 0;
for(int i = 0; i < 9; ++i)
{
int tmp = 0;
for(int j = i+1; j < 9; ++j)
if(x.s[j/3][j%3] < x.s[i/3][i%3]) ++tmp;
res += tmp*bas[8-i];
}
//has[s] = res;
return res;
}
void print()
{
int cnt = 0, k = 0;
while(ans[k] != -1)
{
sa[cnt++] = pnt[ans[k]];
k = pre[k];
}
sa[cnt] = '\0';
reverse(sa, sa+cnt);
printf("%s\n", sa);
}
void Astar()
{
//has.clear();
memset(vis, 0, sizeof vis);
while(!q.empty())
{
x = q.top(); q.pop();
int uid = getNo();
if(uid == 0)
{
print(); return;
}
for(int i = 0; i < 4; ++i)
{
int tx = x.posx + nxt[i][0];
int ty = x.posy + nxt[i][1];
if(tx < 0 || tx >= 3 || ty < 0 || ty >= 3) continue;
int tposx = x.posx, tposy = x.posy;
swap(x.s[x.posx][x.posy], x.s[tx][ty]);
int vid = getNo();
if(!vis[vid])
{
vis[uid] = true;
x.posx = tx, x.posy = ty;
ans[vid] = i, pre[vid] = uid;
x.h = H(), ++x.t;
q.push(x);
--x.t;
x.posx = tposx, x.posy = tposy;
}
swap(x.s[x.posx][x.posy], x.s[tx][ty]);
}
}
}
int main()
{
init();
//freopen("in.txt", "r", stdin);
while(scanf(" %c", &ch) != EOF)
{
if(ch != 'x') x.s[0][0] = ch-1;
else x.s[0][0] = '8', x.posx = 0, x.posy = 0;
for(int i = 1; i <= 8; ++i)
{
scanf(" %c", &ch);
if(ch != 'x') x.s[i/3][i%3] = ch-1;
else x.s[i/3][i%3] = '8', x.posx = i/3, x.posy = i%3;
}
int sid = getNo();
pre[sid] = -1, ans[sid] = -1;
x.h = H(), x.t = 0;
while(!q.empty()) q.pop();
q.push(x); vis[sid] = true;
if(!isok() && sid != 0) puts("unsolvable");
else Astar();
}
return 0;
}
双向bfs代码:
#include <algorithm>
#include <iostream>
#include <string.h>
#include <string>
#include <cstdio>
#include <queue>
using namespace std;
const int maxn = 4e5;
int nxt[4][2] = {0, 1, 1, 0, 0, -1, -1, 0};
char pnt[] = "rdlu";
struct node
{
int posx, posy, t;
char s[3][3];
} x;
queue<node> q[2];
int bas[10];
int ans[2][maxn], pre[2][maxn];
char ch, sa[maxn];
bool vis[2][maxn], flag;
void init()
{
bas[0] = 1;
for(int i = 1; i < 9; ++i)
bas[i] = bas[i-1]*i;
}
bool isok()
{
int res = 0;
for(int i = 0; i < 9; ++i)
for(int j = i+1; j < 9; ++j)
{
if(x.s[i/3][i%3] != '8' && x.s[j/3][j%3] != '8' &&
x.s[i/3][i%3] > x.s[j/3][j%3])
++res;
}
return !(res&1);
}
int getNo()
{
int res = 0;
for(int i = 0; i < 9; ++i)
{
int tmp = 0;
for(int j = i+1; j < 9; ++j)
if(x.s[j/3][j%3] < x.s[i/3][i%3]) ++tmp;
res += tmp*bas[8-i];
}
return res;
}
void print(int k)
{
int cnt = 0, t = k;
while(ans[0][t] != -1)
{
sa[cnt++] = pnt[ans[0][t]];
t = pre[0][t];
}
reverse(sa, sa+cnt);
t = k;
while(ans[1][t] != -1)
{
sa[cnt++] = pnt[(ans[1][t]+2)%4];
t = pre[1][t];
}
sa[cnt] = '\0';
printf("%s\n", sa);
}
void bfs(int id)
{
x = q[id].front(); q[id].pop();
int uid = getNo();
for(int i = 0; i < 4; ++i)
{
int tx = x.posx + nxt[i][0];
int ty = x.posy + nxt[i][1];
if(tx < 0 || tx >= 3 || ty < 0 || ty >= 3) continue;
int tposx = x.posx, tposy = x.posy;
swap(x.s[x.posx][x.posy], x.s[tx][ty]);
int vid = getNo();
if(!vis[id][vid])
{
vis[id][vid] = true;
x.posx = tx, x.posy = ty;
ans[id][vid] = i, pre[id][vid] = uid;
++x.t;
if(vis[id^1][vid])
{
flag = true;
print(vid); return;
}
q[id].push(x);
--x.t;
x.posx = tposx, x.posy = tposy;
}
swap(x.s[x.posx][x.posy], x.s[tx][ty]);
}
}
void work()
{
while(!q[0].empty()) q[0].pop();
while(!q[1].empty()) q[1].pop();
memset(vis, 0, sizeof vis);
int sid = getNo();
pre[0][sid] = -1, ans[0][sid] = -1;
x.t = 0; q[0].push(x);
vis[0][sid] = true;
for(int i = 0; i < 9; ++i) x.s[i/3][i%3] = i+'0';
pre[1][0] = -1, ans[1][0] = -1;
x.posx = 2, x.posy = 2;
x.t = 0; q[1].push(x);
vis[1][0] = true;
flag = false;
while(!q[0].empty() && !q[1].empty())
{
bfs(0);
if(flag) break;
bfs(1);
if(flag) break;
}
}
int main()
{
init();
//freopen("in.txt", "r", stdin);
while(scanf(" %c", &ch) != EOF)
{
if(ch != 'x') x.s[0][0] = ch-1;
else x.s[0][0] = '8', x.posx = 0, x.posy = 0;
for(int i = 1; i <= 8; ++i)
{
scanf(" %c", &ch);
if(ch != 'x') x.s[i/3][i%3] = ch-1;
else x.s[i/3][i%3] = '8', x.posx = i/3, x.posy = i%3;
}
if(!isok() && getNo() != 0) puts("unsolvable");
else work();
}
return 0;
}
继续加油~