有一个n*m(n和m都不超过50)的棋盘,有k个目标格子需要访问。需要访问的格子的横纵坐标存放在数组x[]和y[]中(0<=x[i]<n, 0<=y[i]<m)。
遍历的规则为:- 每一步只能从一个目标格子水平或者竖直跳跃移动到另一个目标格子。
- 连续的两步必须形成直角。即如果前一步是水平移动,那么下一步只能竖直移动。
问是否存在一种遍历顺序,使得每个目标格子有且仅被访问一次。
样例:k=8, x=[0, 0, 0, 0, 2, 2, 4, 4], y=[0, 2, 4, 6, 4, 6, 2, 4],对应于下图中A, B, C, D, F, E, G, H 8个目标格子,存在满足条件的遍历A->D->E->F->C->B->G->H。
想了很久,终于想到归结到欧拉路径上了。
第一步当然是盼是否连通,然后第二步是判断欧拉路径的充要条件:
一个无向图存在欧拉路径,当且仅当
该图所有顶点的度数为偶数
或者
除了两个度数为奇数外其余的全是偶数
。
但是在这里,连接关系是有水平跟竖直两种,怎么把这个跟欧拉路径的度数结合起来,我没想通,这里参考了别人的一个观点:
用 a 表示含有奇数个元素的行数, b 表示含有奇数个元素的列数, 那么 a+b 一定是偶数. 如果 a+b > 2, 那么不可能遍历, 理由类似于欧拉回路 把位于同一行或者同一列的点都连线, 如果整个图是不连通的, 那么也显然不能遍历. 剩下的就可以遍历了, 这可以用归纳法证明.
然后借用别人的这个总结,就可以编写代码了:
const int MAXN=51;
int graph[MAXN][MAXN];
bool isConnected(int x,int y );
void dfs (int x,int y );
bool existPath(vector<int> &x, vector<int> &y) {
int nx=x.size(),ny=y.size();
if ( nx<=1 )
return true;
memset(graph,0,sizeof(graph));
for(int i=0;i<nx;i++)
graph[x[i]][y[i]]=1;
if (!isConnected(x[0],y[0]) )
return false;
int a=0,b=0;
for(int i=0;i<MAXN;i++)
{
int num=0;
for(int j=0;j<MAXN;j++)
if ( graph[i][j]>0 )
num++;
if ( num&1 )
a++;
int tnum=0;
for(int j=0;j<MAXN;j++)
if ( graph[j][i] >0 )
tnum++;
if ( tnum&1 )
b++;
}
return (a+b)<=2;
}
void dfs (int x,int y )
{
if (graph[x][y]!=1 )
return;
graph[x][y]=2;
for(int i=0;i<MAXN;i++)
{
if ( graph[x][i]==1 )
dfs(x,i);
if ( graph[i][y]==1 )
dfs(i,y);
}
}
bool isConnected(int x,int y )
{
dfs(x,y);
for(int i=0;i<MAXN;i++)
for(int j=0;j<MAXN;j++)
{
if ( graph[i][j]== 2)
graph[i][j]=1;
else if ( graph[i][j]==1 )
return false;
}
return true;
}