分析:
在本题中,每个单词只有首尾两个字母很关键,并且每个单词可以看成连接首尾两个字母的一条有向边(由首字母指向尾字母)。这样每个测试数据中的一组单词可以构造成一个图:图中的顶点为26个小写字母,每个单词为图中的一条边。例如,本题样例输入中两个测试数据所构造的有向图如图所示。
构造好有向图后,题目要判定是否可以经过重组使得每个单词第一个字母跟前一个单词最后一个字母相同,等效于判断图中是否存在一条路径经过每条边一次且仅一次,这就是有向欧拉通路。本题的处理方法是:
(1) 读入每个单词时,因为每个单词相当于一条从首字母指向尾字母的边,所以对单词首字母对应的顶点,出度加1;尾字母对应的顶点,入度加1;
(2) 26 个顶点的入度和出度都统计完毕后,根据各顶点的出度、入度关系来判断是否存在欧拉通路,但要注意排除每个单词的首尾字母中没有出现过的字母。在下面的代码中,用bused[ ]数组来表示每个字母是否在单词的首尾中出现。例如,在图(a)中,只有3个字母对应有顶点,其他23个字母都没有对应顶点。
(3) 判断完以后,还得判断整个有向图的基图(即不考虑边的方向)是否连通,如图(b)所示,每个顶点的出度、入度都相等,但这个图的基图是不连通的,所以也不存在有向欧拉通路。判断连通最好的方法是使用并查集:考察图中所有的边(u, v),如果u和v不相同,且u和v不在同一个连通分量上,则合并u和v各自所在的连通分量;处理完毕后,再判断每个顶点的parent[ ]值是否相同,如果相同的话,说明这个有向图是连通的,否则不连通。
欧拉回路的基本定理:
http://blog.csdn.net/hearthougan/article/details/14055717http://
#include <iostream>
#include <cstring>
using namespace std;
const int MAXN = 100010;
const int INF = 10000000;
int T;
int N;
char word[1010];
int od[26], id[26];//每个顶点出度,入度;
int bused[26];//bused[i]表示第i个字母在这组单词中是否作为首尾字母
int parent[26];//顶点所在集合的根节点
int kcount[26];//每个顶点子节点的个数
struct Edge
{
int u, v;
}edges[MAXN];
void UFset()
{
for(int i = 0; i < 26; ++i)
parent[i] = i, kcount[i] = 0;
}
/**
int Find( int x )//递归
{
if( x != parent[x] )
parent[x] = Find( parent[x] );//压缩路径
return parent[x];
}
*/
int Find( int x )//非递归
{
int s;
for(s = x; s != parent[s]; s = parent[s])//压缩路径
;
while( s != x )
{
int tmp = parent[x];
parent[x] = s;
x = tmp;
}
return s;
}
//将两个不同集合的元素进行合并,是两个集合中的任意元素都是连通的
void Union( int x, int y )
{
int r1 = Find( x );
int r2 = Find( y );
if(kcount[r1] > kcount[r2])
{
parent[r2] = r1;
kcount[r1] += kcount[r2];
}
else
{
parent[r1] = r2;
kcount[r2] += kcount[r1];
}
}
//判断有向图的基图是否连通
bool bconnect()
{
int u, v, i;
UFset();
for( i = 0; i < N; ++i)//对于每条边(u, v), 如果u和v不属于同一个连通分量,则合并
{
u = edges[i].u;
v = edges[i].v;
if( u != v && Find(u) != Find(v))
Union(u, v);
}
int first = -1;//第一个bused[i]不为零的顶点,也是集合中树的根节点
for(i = 0; i < 26; ++i)
{
if(bused[i] == 0)
continue;
if(first == -1)//找到根节点
first = i;
else if( Find( i ) != Find( first ))//如果该定点不与根节点在同一个集合则,构不成欧拉通路
break;
}
if(i < 26)//不连通
return false;
else
return true;
}
int main()
{
int i;
int u, v;
cin>>T;
while(T--)
{
memset(od, 0, sizeof(od));
memset(id, 0, sizeof(id));
memset(bused, 0, sizeof(bused));
cin>>N;
for(i = 0; i < N; ++i)
{
cin>>word;
u = word[0] - 'a';
v = word[ strlen(word) - 1] - 'a';
od[u]++;
id[v]++;
bused[u] = bused[v] = 1;
edges[i].u = u;
edges[i].v = v;
}
bool Euler = true;//是否存在欧拉通路
int one = 0;//顶点的出度大于顶点入度的顶点个数
int none = 0;//顶点入度大于顶点出度的顶点个数
for(i = 0; i < 26; ++i)//根据定理判断
{
if( bused[i] == 0 )
continue;
if(od[i] - id[i] >= 2 || id[i] - od[i] >= 2)
{
Euler = false;
break;
}
if(od[i] == 0 && id[i] == 0)
{
Euler = false;
break;
}
if(od[i] - id[i] == 1)
{
one++;
if(one > 1)
{
Euler = false;
break;
}
}
if(id[i] - od[i] == 1)
{
none++;
if(none > 1)
{
Euler = false;
break;
}
}
}
if(one != none)
Euler = false;
if( !bconnect() )
Euler = false;
if( Euler )
cout<<"Ordering is possible."<<endl;
else
cout<<"The door cannot be opened."<<endl;
}
return 0;
}