题目描述:
给定n张骨牌,每张骨牌有左右两个点数(从1到6)。问能不能通过交换骨牌的顺序和交换左右2个点数,使得任意两个相邻骨牌的相邻段为相等的数字。
输入描述:
输入文件中有多个测试数据。每个测试数据的第一行为一个整数N,1≤N≤100,表示多米诺骨牌的总数;接下来N行对每一张牌进行描述:每一行表示一张牌的左右两段的数字(1到6),用空格隔开。
输入文件最后一行为0,表示输入结束。
输出描述:
对输入文件中的每个测试数据,如果不可能满足要求,输出"No solution";如果有解,输出任意一个即可:每张牌必须从左到右打印;每一行都要包含当前牌的序号(第几张牌)和符号"+"或"-"(第一个符号表示不旋转骨牌,第二个表示要旋转)。在每个测试数据的输出之后输出一个空行。
例如,对图(a)给出的5张牌,其中一个解如图(b)所示。这5张牌按照图(b)所示的旋转要求及顺序排列后,可以使得任意两个相邻骨牌的相邻段为相等的数字。
样例输入:
5
1 2
2 4
2 4
6 4
2 1
12
6 1
3 4
4 6
1 3
5 4
2 5
1 2
6 1
1 2
2 6
4 1
1 2
0
样例输出:
2-
5+
1+
3+
4–
12-
11-
5-
6-
10+
8+
9+
7-
4+
2+
3+
1+
分析:
样例输入中第2个测试数据所描述的12张骨牌如下图<一>(a)所示,它的1个解如图(b)所示:这12张骨牌经过重排顺序和适当的旋转某些牌后,使得任意两个相邻骨牌的相邻段为相等的数字。
本题可以转化成欧拉回路或欧拉通路的求解问题。首先要构造成一个图,图的顶点就是6个点数,每张牌对应一条无向边,这条边的两个顶点就是牌的两个点数。例如,对样例输入中的两个测试数据,构造好的图如图<二>(a)和(b)所示。
图<一>
图<二>
图构造好以后,先判断是否存在欧拉通路或欧拉回路。如果存在欧拉通路或欧拉回路的话,则选择一个正确的起始顶点,采用DFS算法从该顶点开始遍历:从该顶点选择一条未访问过的边,访问该条边后达到另一个邻接顶点;然后又从这个顶点出发选择一条未访问过的边进行访问;…;直到所有边都访问一遍为止。在这个过程中,如果遇到死胡同就回退。将每条边的访问顺序记录下来,这组边的排列就组成了一条欧拉通路。
起始顶点的选取:如果存在欧拉回路,可以取该回路上的任一顶点,但不要选取不在该回路上的顶点(如在图<二>(a)中,不能选择顶点3和顶点5);如果只存在欧拉通路,则只能选奇度顶点。
图的存储结构的设计:由于存在平行边(或称为重边,即多条边起点和终点一样),只能采用邻接表来存储;采用邻接表存储的另一个好处是因为输出欧拉通路时要指明每条边是正向还是反向,而在构造无向图的邻接表时,每条边对应2个边结点,因此可以在边结点结构中增加一个分量来表示正向和反向;另外边结点中还应增加一个分量,用来保存边的序号。在本题中,针对这些需求所设计的简易邻接表详见下面的代码,对样例输入中的第1个测试数据所构造的邻接表如下图所示。在下图中,每个边结点有4个分量,分别是:边的另一个邻接点的序号、边的序号(即读入时的顺序)、边的标记(正向或反向)、指向下一个边结点的指针。
多米诺骨牌:邻接表的构造
另外,在下面的代码中,有两个数组很关键:
visited[ ]数组:与第2章介绍DFS算法时引入的visited[ ]数组类似但又有区别的是,此时 visited[ ]数组用来记录各边的访问标志,为1表示已经访问过,初始时为0;
path[ ]数组:用来存储欧拉通路(回路)上各边的序号,如果path[i] = j,j>0 表示欧拉通路上第i 条边为第j 块骨牌,且不旋转,path[i] = -j表示要旋转
#include <iostream>
#include <cstring>
using namespace std;
const int MAXN = 110;
struct EdgeNode
{
int adjvex; //边的另一个邻接顶点序号
int EdgeNumber; //边的序号,即读入时的顺序;
int flag; //标记:1--正向, -1--反向
EdgeNode *nextedge; // 指向下一个边结点
};
EdgeNode* EdgeLink[MAXN];//各个顶点的边链表(第0个元素不用)
int visited[MAXN];//边访问标志,为1表示已经访问过,初始全部为0
int path[MAXN];//如果path[i] = j; j > 0表示欧拉通路上第i条边为第j块骨牌,且不旋转,path[i] = -j表示要旋转
int pi;//path数组的下标
int N;//每个测试中,骨牌的数目
void CreateLG()//采用邻接表构建有向图G
{
int i;
EdgeNode *p1, *p2;
int v1, v2;
memset( visited, 0, sizeof(visited) );
for( i = 1; i <= 6; ++i )
EdgeLink[i] = NULL;
int number = 1;
for(i = 1; i <= N; ++i)
{
cin>>v1>>v2;
p1 = new EdgeNode;
p2 = new EdgeNode;
p1->adjvex = v2;
p1->EdgeNumber = number;
p1->flag = 1;
p1->nextedge = EdgeLink[v1];
EdgeLink[v1] = p1;
p2->adjvex = v1;
p2->EdgeNumber = number;
p2->flag = -1;
p2->nextedge = EdgeLink[v2];
EdgeLink[v2] = p2;
number++;
}
}
void DFS( int start )
{
while(pi <= N )
{
EdgeNode *p;
p = EdgeLink[ start ];
while( p != NULL )
{
if( !visited[ p->EdgeNumber ])//p->EdgeNumber边从未访问过
{
visited[ p->EdgeNumber ] = 1;
if(p->flag > 0)
path[pi] = p->EdgeNumber;//正向边,不用旋转
else
path[pi] = -(p->EdgeNumber);//要旋转
pi++;
DFS(p->adjvex);
}
else
p = p->nextedge;
}
}
}
void Domino()
{
int JDNum = 0;//奇度顶点的个数
int start1, start2;//搜索的起点,如果存在奇度顶点,start1是其起始点,如果不存start2是该有向图深度搜索的起始点
EdgeNode *p;
int i;
for(i = 1; i <= 6; ++i)
{
int DNum = 0;
p = EdgeLink[i];
while(p != NULL )
{
DNum++;
p = p->nextedge;
}
if( DNum % 2 != 0)
{
JDNum++;
start1 = i;
}
if(DNum != 0)
start2 = i;
}
if(JDNum != 0 && JDNum != 2)//根据欧拉通路的基本定理,不存在欧拉通路
{
cout<<"No Solution!"<<endl;
return;
}
pi = 1;
if(JDNum == 2)//欧拉通路,start1是其起始点
DFS(start1);
else //欧拉回路
DFS(start2);
char flag1 = '+', flag2 = '-';
for(i = 1; i <= N; ++i)
{
if( path[i] > 0)
cout<<path[i]<<flag1<<endl;
else
cout<<-path[i]<<flag2<<endl;
}
}
void DeleteLG()
{
int i;
EdgeNode *p;
for(i = 1; i <= 6; ++i)
{
p = EdgeLink[i];
while( p != NULL)
{
EdgeLink[i] = p->nextedge;
delete p;
p = EdgeLink[i];
}
}
}
int main()
{
while((cin>>N) && N)
{
CreateLG();
Domino();
DeleteLG();
}
return 0;
}