解法一:与POJ3009 类似,都要先置位,(然后执行本次要完成的任务),最后复位!!
#include<iostream>
using namespace std;
typedef class
{
public:
int row;
char col;
}location;
int p,q; //chess size = p*q
//数字是行p,字母是列q
bool chess['Z'+1][27];
int x,y; //返回值
void path(int i,int j,int num) //ij为骑士当前在棋盘的位置
{ //num为骑士即将要跳到的位置序号
switch(num)
{
case 1: {x=i-1; y=j-2; break;} //注意这个尝试跳的顺序不能错
case 2: {x=i+1; y=j-2; break;} //因为题目要求是字典序lexicographically输出
case 3: {x=i-2; y=j-1; break;} //这个顺序错了,必定WA
case 4: {x=i+2; y=j-1; break;}
case 5: {x=i-2; y=j+1; break;}
case 6: {x=i+2; y=j+1; break;}
case 7: {x=i-1; y=j+2; break;}
case 8: {x=i+1; y=j+2; break;}
}
return;
}
bool DFS(location* way,int i,int j,int step)
{
chess[i][j]=true;
way[step].row=i;
way[step].col=j;
if(step==way[0].row)
return true;
for(int k=1;k<=8;k++) //骑士从当前位置尝试跳到其他位置
{
path(i,j,k);
int ii=x,jj=y;
if(!chess[ii][jj] && ii>=1 && ii<=p && jj>='A' && jj<='A'+q-1)
if(DFS(way,ii,jj,step+1))
return true;
}
chess[i][j]=false; //能执行到这步,说明前面跳的8步都不符合要求
return false; //即当前位置是错误位置,擦除记录返回上一步
}
int main(void)
{
int test;
cin>>test;
int t=1;
while(t<=test)
{
/*Initial*/
memset(chess,false,sizeof(chess));
cin>>p>>q;
if(p==1 && q==1) //范围缩窄,不要也能AC
{
cout<<"Scenario #"<<t++<<':'<<endl;
cout<<"A1"<<endl<<endl;
continue;
}
if(p*q>26 || p>=9 || q>=9 || p<=2 || q<=2) //范围缩窄,不要也能AC
{
cout<<"Scenario #"<<t++<<':'<<endl;
cout<<"impossible"<<endl<<endl;
continue;
}
location* way=new location[p*q+1]; //记录走过的位置坐标
way[0].row=p*q; //记录总步数(棋盘总格子数)
/*DFS*/
bool flag=false;
for(int j='A';j<='A'+q-1;j++)
{
for(int i=1;i<=p;i++)
if(DFS(way,i,j,1))
{
cout<<"Scenario #"<<t++<<':'<<endl;
for(int k=1;k<=way[0].row;k++)
cout<<way[k].col<<way[k].row;
cout<<endl<<endl;
flag=true;
break;
}
if(flag)
break;
}
if(!flag)
{
cout<<"Scenario #"<<t++<<':'<<endl;
cout<<"impossible"<<endl<<endl;
}
}
return 0;
}
下面这张图解释在涂白之后会不会重复记录( way [ step ] )的问题,答案是不会!
跳马是双向的,因此是无向图。这就注定了,当某个结点由黑被涂白之后,是不可能再被重新访问的。
如图中的结点3,假设它没有发现4,导致其被涂白回溯到结点2,此时由结点2再访问结点4,因为结点3已经被涂白了,由结点4就会再次重复访问记录结点3。
这种担心是多余的,或者说根本不可能发生。因为既然由4能访问到3,那么由3也必然能访问到4,就不可能漏掉这个结点,所以假设根本就不成立,故不可能重复访问。
解法二:
先上代码poj2488
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
using namespace std;
#define maxn 26
struct Point
{
int x, y;
} way[maxn * maxn];
bool map[maxn][maxn];
int p, q;
bool found;
int dir[8][2] =
{
{ -2, -1 },
{ -2, 1 },
{ -1, -2 },
{ -1, 2 },
{ 1, -2 },
{ 1, 2 },
{ 2, -1 },
{ 2, 1 } };
bool ok(int x, int y)
{
if (x < 0 || y < 0 || x >= q || y >= p)
return false;
if (map[x][y])
return false;
return true;
}
void dfs(int x, int y, int step)
{
way[step].x = x;
way[step].y = y;
if (step == p * q - 1)
{
found = true;
return;
}
for (int i = 0; i < 8; i++)
if (ok(x + dir[i][0], y + dir[i][1]))
{
map[x + dir[i][0]][y + dir[i][1]] = true;
dfs(x + dir[i][0], y + dir[i][1], step + 1);
if (found)
return;
map[x + dir[i][0]][y + dir[i][1]] = false;
}
}
void print()
{
for (int i = 0; i < p * q; i++)
printf("%c%d", way[i].x + 'A', way[i].y + 1);
printf("\n\n");
}
int main()
{
//freopen("t.txt", "r", stdin);
int t;
scanf("%d", &t);
int s = 0;
while (t--)
{
s++;
printf("Scenario #%d:\n", s);
memset(map, 0, sizeof(map));
scanf("%d%d", &p, &q);
for (int i = 0; i < q; i++)
{
for (int j = 0; j < p; j++)
{
found = false;
map[i][j] = true;
dfs(i, j, 0);
if (found)
break;
map[i][j] = false;
}
if (found)
break;
}
if (found)
print();
else
printf("impossible\n\n");
}
return 0;
}
下面以“图“来说明DFS的执行过程,约定
1)涂黑即为map置1,表示方格已访问过,反之白色为map复位,表示返回到上一层或者未访问过的方格;、
2)不带圆圈的数字(实质是 way[step] 的”填充“过程)表示涂黑的顺序,带圆圈的表示由黑转白的顺序。
注:1)如果我们最终无法找到一个完整的路径覆盖所有的方格,即step< p * q - 1,最终的结果一定是所有已涂黑的结点全部转为白色,供下一次的递归入口(i , j)重新使用(这也是为什么转白的原因),至于本次的递归入口也会转白,不过是在主函数中完成的:map[i][j] = false ; 每次进入递归入口时step都会初始化为0,dfs(i, j,0);;
2)如果找到了则最终的结点颜色分布可能是下面的两种情况中的一种:要么所有的结点都是黑色,要么有的结点是黑色有的结点是由黑转白变成的白色(这是由回溯造成的),而绝无可能一直是白色。只要知道了完整路径,我们是不在乎颜色分布的,因为一旦step= p * q - 1,就会一路return ; ,直到返回到主函数中 print( ); 完成本次使命;
3)所有的涂黑(从上一层进入时)和黑转白(退回到上一层时)操作都是由其前驱结点来完成的;每次都是先把控制权交由上一层递归(说明下面没有后继结点了,“回溯”),然后再由黑转白,这两件事必然要发生都发生。
如上图中,②要变白之前,先返回到上一层递归,即1所在处,把控制权交给1,然后由黑转白;
4)一旦进入每次的递归入口,step就会一直累加,而不会被覆盖,即使在”回溯“的情况下。