洛谷P1127 词链 欧拉路问题

题目描述:

如果单词 XXX 的末字母与单词 YYY 的首字母相同,则 XXX 与 YYY 可以相连成 X.YX.YX.Y。(注意:XXX、YYY 之间是英文的句号 .)。例如,单词 dog 与单词 gopher,则 dog 与 gopher 可以相连成 dog.gopher。
另外还有一些例子:
• dog.gopher
• gopher.rat
• rat.tiger
• aloha.aloha
• arachnid.dog
连接成的词可以与其他单词相连,组成更长的词链,例如:
aloha.arachnid.dog.gopher.rat.tiger
注意到,. 两边的字母一定是相同的。
现在给你一些单词,请你找到字典序最小的词链,使得这些单词在词链中出现且仅出现一次。

输入格式:
第一行是一个正整数 n(1≤n≤1000),代表单词数量。
接下来共有 n 行,每行是一个由 1 到 20 个小写字母组成的单词。

输出格式:
只有一行,表示组成字典序最小的词链,若不存在则只输出三个星号 ***。

输入输出样例
输入 #1
6
aloha
arachnid
dog
gopher
rat
tiger
输出 #1
aloha.arachnid.dog.gopher.rat.tiger

说明/提示
• 对于 40% 的数据,有 n≤10;
• 对于 100% 的数据, n≤1000。

分析:

每个单词都有头字母和尾字母,不难发现,每条词链都有这样的特点:
除第一个单词外,其余单词的头字母必然有一个尾字母与其对应。
除最后一个单词外,其余单词的尾字母必然有一个头字母与其对应。
显然是一个欧拉路问题。

问题转化为:

一张图中有26(即26个字母,允许有孤立点)个结点,n条有向路(即n个单词),寻找一条欧拉路,使得经过的词链最短。

解决前思考的几个小问题:
问题1:是否有解?记录每个字母的入度和出度,按照欧拉路的判断方法就可以判断,但是要注意环的存在。

问题2:何时词链字典序最小?注意到”.”的ASCII码为46,小于所有字母的ASCII码,所以ab.ba<ba.ab abcd.de < abcdd.de 其实只要把字典序小的尽量排在前面就OK

问题3:如何快速找到某个点的后继边?
最好建立一个索引集。在按字典序将所有字母排序之后,以某个字母为首字母的所有单词都在一个固定区间上

问题4:如何遍历?类似深度优先搜索,每次尽量选择字典序最小的边进行递归。

伪代码:

/*伪代码:*/
int main()
{
	Input();//输入数据
	sort();//排序
	SetPtr();//建立索引集
	if(! IsEulerRoad())	//假如不是欧拉路(但由于环的存在,其实并非充分条件,后面还要判断)
	{
		cout <<***<< endl;
		return 0;
	}
	Travel();	//遍历,并记录答案
	Output();//输出
	return 0;
}


完整AC代码:

#include<iostream>
#include<string>
#include<algorithm>
#include<list>
using namespace std;

string words[1002];//记录所有单词
int mark[1001];//遍历时作标记
int num = 0;//边(即单词)的总数
int Ptr[28];  //索引集,记录words[] 中以26个字母为首字母的第一个单词下标
int Begin;//遍历起点
int ans[1002];//存储遍历路径
int flag = false;//标记是否遍历结束

void SetPtr();	//初始化Ptr
void travel(int s,int depth);
int InDegree[28];//26个字母的入度
int OutDegree[28];//26个字母的出度
bool IsEulerRoad();
int main()
{
	cin >> num;
	for(int i = 0; i < num; ++i)
	{
		cin >> words[i];
		char apple = words[i][0];
		char banana = words[i][words[i].size()-1];

		++OutDegree[apple - 'a'];
		++InDegree[banana - 'a'];
	}
	sort(words,words+num);//按字典序排序
	SetPtr();
		if(!IsEulerRoad())
	{
		cout << "***" << endl;
		return 0;
	}


	travel(Begin,1);
	if(!flag)//排除回路的干扰。比如输入数据为:2 a b。 
	{
		cout << "***" <<endl;
		return 0;
	}
	cout << words[ans[0]];
	for(int i = 1; i < num; ++i)
	{
		cout << "." << words[ans[i]];
	}
	cout << endl;
	return 0;
}
void SetPtr()
{
	int j = 0;
	for(int i = 0; i < num; ++i)
	{
		int k = (char)(words[i][0]) - 'a';
			if(k < j)
				continue;
				while(k > j)
				{
					Ptr[j] = i;
					++j;
				}
				Ptr[j] = i;
				++j;
	}
	for(;j < 27;++j )
		Ptr[j] = num;
	return;
}
void travel(int s,int depth)
{
	ans[depth - 1] = s;
	if(depth == num)
	{
		flag = true;
		return;
	}
	mark[s] = 1;//标记
	int next = (words[s][words[s].size()-1])-'a';
	int start = Ptr[next];
	int end = Ptr[next+1];//索引集派上用场
	for(int itr = start; itr < end; ++itr)//遍历所有边
	{
		if(!mark[itr])
		{
			travel(itr,depth+1);
			mark[itr] = 0;
			if(flag)
				return;//防止继续循环覆盖掉正确答案
		}
	}


}
bool IsEulerRoad()
{
	int Count = 0;
	int In = 0, Out = 0;
	for(int i = 0; i < 26; ++i)
	{
		if(OutDegree[i] != InDegree[i])
		{
			++Count;
			if(OutDegree[i] > InDegree[i])
			{
				Out = OutDegree[i] - InDegree[i];
				Begin = Ptr[i];//出度大的点应该为起点。假如这句没有执行,说明这张图可能是欧拉图,Begin=0也是正确的;
			}
			else
			{
					In = InDegree[i] - OutDegree[i];
			}
		}
	}
	if(!Count)
		return true;
	if(Count != 2)
		return false;
	return ((Out == 1)&&(In == 1));
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值