一、题目
http://acm.hdu.edu.cn/showproblem.php?pid=1043
二、两种方法
该题很明显,是一个八数码的问题,就是9宫格,里面有一个空格,外加1~8的数字,任意一种情况,如果能通过移动空格使数码组成
1 2 3
4 5 6
7 8 0
的形式,就输出变换的序列,如果不能,输出unsolvable.
逆向$BFS$+康托展开
1.什么是康托展开
https://zh.wikipedia.org/wiki/%E5%BA%B7%E6%89%98%E5%B1%95%E5%BC%80
$wiki$讲解比较好。
有了康托展开,你可能会有疑问,要它有何用?但如果你联系一下$hash$函数,康托展开能够保证每一种八数码的情况都能够用一个整型数字唯一表示,这样是不是就好理解了?
2.逆向BFS
为了求出它的变换序列,我们可以逆向思维想一下,如果我从最终结果出发去变换,然后把路途中的每一种情况都记录下来(有了康托展开对八数码进行$hash$,会很方便),记录变换路径,然后对输入的其实条件直接进行输出就可以了。
这就是逆向$BFS$的解决方案。
这里需要注意的是,如果用STL的queue以及string,可能会造成超内存。解决办法就是用个数组模拟即可。
#include <vector>
#include <cstdio>
#include <iostream>
#include <fstream>
#include <queue>
#include <cstring> using namespace std; const int MAXN = ;
const int fac[] = {, , , , , , , , , }; //factorial
const int dx[] = {-, , , };
const int dy[] = {, , , -};
const char op[] = "dulr"; //operation
vector<char> Path[MAXN]; //记录路径
bool Visit[MAXN]; //标记数组
struct Node
{
int S[]; //二维的数码表一维表示(下标从1开始)
int loc; //9 = x loaction
int cat; //对应的康拓展开值
};
Node Q[MAXN];
int Cantor(int s[])
{
int t, ans = ;
for(int i = ; i < ; i++)
{
t = ;
for(int j = i+; j < ; j++)
{
if(s[j] < s[i])
t++;
}
ans += t*fac[-i-];
}
return ans;
} void BFS()
{
memset(Visit, , sizeof(Visit));
int x, y, Cnt = , Rea = ;
Node cur, t;
for(int i = ; i < ; i++)
cur.S[i] = i+;
cur.S[] = ;
cur.loc = ;
cur.cat = Cantor(cur.S);
//Path[cur.cat] = "";
Visit[cur.cat] = ;
Q[Cnt++] = cur; while(Rea < Cnt)
{ t = Q[Rea];
Rea++;
for(int i = ; i < ; i++)
{
x = t.loc/ + dx[i];
y = t.loc% + dy[i];
if(x < || x > || y < || y > )
continue;
cur = t; //**
cur.loc = x*+y;
cur.S[t.loc] = t.S[cur.loc]; //交换
cur.S[cur.loc] = ; //X
cur.cat = Cantor(cur.S);
if(!Visit[cur.cat])
{
Visit[cur.cat] = ;
Path[cur.cat] = Path[t.cat];
Path[cur.cat].push_back(op[i]);
//Path[cur.cat] = op[i] + Path[t.cat];
Q[Cnt++] = cur; }
}
}
} int main()
{
//freopen("input.txt", "r", stdin);
//freopen("out.txt", "w", stdout);
int s[];
char c[];
BFS(); while(scanf("%s", c)!=EOF)
{
if(c[] == 'x')
s[] = ;
else
s[] = c[] - '';
for(int i = ; i < ; i++)
{
scanf("%s", c);
if(c[] == 'x')
s[i] = ;
else
s[i] = c[] - '';
}
int Cat = Cantor(s);
if(Visit[Cat])
{
for(int i = Path[Cat].size()-; i >= ; i--)
printf("%c", Path[Cat][i]);
printf("\n");
}
else
printf("unsolvable\n");//cout << "unsolvable" << endl;
}
return ;
}
AC代码
3.A*
http://www.cnblogs.com/me-sa/archive/2010/05/18/A-Star-Pathfinding-for-Beginners.html
基本A*算法的入门讲解都是这个。其实当你认真体会后,A*算法的关键就是F=G+H中的G,H如何算的问题,其他的与搜索大同小异,因为有了G,H,就可以有目的性的去搜索,也就是启发式搜索。(仅个人理解)
这个题目里,求H依然采用的是曼哈顿距离,即每个每个数从当前位置到它最终位置的曼哈顿距离。
这里,还用到了小技巧,就是逆序数,当在满足上述约定的八数码问题中,空格与相邻棋子的交换不会改变棋局中棋子数列的逆序数的奇偶性。因为最终情况的逆序数是偶数,所以要保证每次搜索过程中逆序数都是偶数。这样就达到了剪枝。
需要注意的是,求逆序数,无论空格是代表的0还是9,都不要考虑进去。
推广一下:对于N*M数码问题,空白在同一行交换不会导致奇偶性互变;上下行交换,如果列为奇数,则不会导致奇偶性互变;如果列数为偶数,则会导致奇偶性互变,所以此时还要考虑上下行交换的次数,综合得出答案。
/*逆向BFS*/
/*
#include <vector>
#include <cstdio>
#include <iostream>
#include <fstream>
#include <queue>
#include <cstring> using namespace std; const int MAXN = 370000;
const int fac[] = {1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880}; //factorial
const int dx[] = {-1, 1, 0, 0};
const int dy[] = {0, 0, 1, -1};
const char op[] = "dulr"; //operation
vector<char> Path[MAXN]; //记录路径
bool Visit[MAXN]; //标记数组
struct Node
{
int S[9]; //二维的数码表一维表示(下标从1开始)
int loc; //9 = x loaction
int cat; //对应的康拓展开值
};
Node Q[MAXN];
int Cantor(int s[])
{
int t, ans = 1;
for(int i = 0; i < 9; i++)
{
t = 0;
for(int j = i+1; j < 9; j++)
{
if(s[j] < s[i])
t++;
}
ans += t*fac[9-i-1];
}
return ans;
} void BFS()
{
memset(Visit, 0, sizeof(Visit));
int x, y, Cnt = 0, Rea = 0;
Node cur, t;
for(int i = 0; i < 8; i++)
cur.S[i] = i+1;
cur.S[8] = 0;
cur.loc = 8;
cur.cat = Cantor(cur.S);
//Path[cur.cat] = "";
Visit[cur.cat] = 1;
Q[Cnt++] = cur; while(Rea < Cnt)
{ t = Q[Rea];
Rea++;
for(int i = 0; i < 4; i++)
{
x = t.loc/3 + dx[i];
y = t.loc%3 + dy[i];
if(x < 0 || x > 2 || y < 0 || y > 2)
continue;
cur = t; //**
cur.loc = x*3+y;
cur.S[t.loc] = t.S[cur.loc]; //交换
cur.S[cur.loc] = 0; //X
cur.cat = Cantor(cur.S);
if(!Visit[cur.cat])
{
Visit[cur.cat] = 1;
Path[cur.cat] = Path[t.cat];
Path[cur.cat].push_back(op[i]);
//Path[cur.cat] = op[i] + Path[t.cat];
Q[Cnt++] = cur; }
}
}
} int main()
{
//freopen("input.txt", "r", stdin);
//freopen("out.txt", "w", stdout);
int s[10];
char c[2];
BFS(); while(scanf("%s", c)!=EOF)
{
if(c[0] == 'x')
s[0] = 0;
else
s[0] = c[0] - '0';
for(int i = 1; i < 9; i++)
{
scanf("%s", c);
if(c[0] == 'x')
s[i] = 0;
else
s[i] = c[0] - '0';
}
int Cat = Cantor(s);
if(Visit[Cat])
{
for(int i = Path[Cat].size()-1; i >= 0; i--)
printf("%c", Path[Cat][i]);
printf("\n");
}
else
printf("unsolvable\n");//cout << "unsolvable" << endl;
}
return 0;
}
2 3 4 1 5 0 7 6 8
2 3 0 1 5 4 7 6 8
90747
2 3 4 1 5 0 7 6 8
92307 */ /* A* */ #include <cstdio>
#include <iostream>
#include <cstring>
#include <queue>
#include <fstream> using namespace std; const int MAXN = ;
const int fac[] = {, , , , , , , , , }; //factorial
const int dx[] = {, , , -};
const int dy[] = {, -, , };
const char op[] = "rldu"; //operation
const int Aim = ;
int Pre[MAXN];
char Vp[MAXN];
bool Visit[MAXN];
struct Node
{
int s[];
int cat;
int g, h;
int loc; bool operator < (const Node &t)const
{
if(h == t.h)
return g > t.g;
return h > t.h;
}
}; int getCator(const int s[])
{
int t, ans = ;
for(int i = ; i < ; i++)
{
t = ;
for(int j = i+; j < ; j++)
{
if(s[j] < s[i])
t++;
}
ans += t*fac[-i-];
}
return ans; } int getH(const int s[])
{
int x, y, x0, y0, ans = ;
for(int i = ; i < ; i++)
{
if(s[i])
{
x0 = i/, y0 = i%;
x = (s[i]-)/, y = (s[i]-)%; //这里要注意
ans += abs(x-x0) + abs(y-y0); //曼哈顿距离
}
}
return ans;
} bool judge(const int S[]) //判断逆序对数是否为偶数
{
int ans = ;
for(int i = ; i < ; i++)
{
for(int j = i+; j < ; j++)
{
if( S[j] && S[i] && S[j] < S[i] )
ans++;
}
}
if(ans% == )
return true;
else
return false;
} void astar(Node cur)
{
memset(Visit, , sizeof(Visit));
memset(Pre, -, sizeof(Pre));
int x, y;
priority_queue<Node> PQ;
PQ.push(cur);
Visit[cur.cat] = ;
Pre[cur.cat] = -; while(!PQ.empty())
{
Node t = PQ.top();
PQ.pop();
for(int i = ; i < ; i++)
{
x = t.loc/ + dx[i];
y = t.loc% + dy[i];
if(x < || x > || y < || y > )
continue;
cur = t;
cur.loc = x* + y;
cur.s[t.loc] = t.s[cur.loc];
cur.s[cur.loc] = ;
cur.cat = getCator(cur.s); if(Visit[cur.cat] == && judge(cur.s))
{ Visit[cur.cat] = ;
cur.h = getH(cur.s);
cur.g++;
Pre[cur.cat] = t.cat;
Vp[cur.cat] = op[i];
if(cur.cat == Aim)
return;
PQ.push(cur);
}
}
}
} void Print()
{
int c = Aim;
string ans = "";
while(Pre[c] != -)
{
ans = Vp[c]+ans;
c = Pre[c];
}
cout << ans << endl;
} int main()
{
//freopen("input.txt", "r", stdin);
//freopen("out.txt", "w", stdout);
Node cur;
char c[];
while(scanf("%s", c)!=EOF)
{
if(c[] == 'x')
{
cur.s[] = ;
cur.loc = ;
}
else
cur.s[] = c[] - '';
for(int i = ; i < ; i++)
{
scanf("%s", c);
if(c[] == 'x')
{
cur.s[i] = ;
cur.loc = i;
}
else
cur.s[i] = c[] - '';
} if(!judge(cur.s))
{
printf("unsolvable\n");
continue;
}
cur.cat = getCator(cur.s);
cur.g = , cur.h = getH(cur.s);
astar(cur);
Print();
}
return ;
}
AC代码