单词接龙(NOIP2000提高组)

单词接龙是一个与我们经常玩的成语接龙相类似的游戏,现在我们已知一组单词,且给定一个开头的字母,要求出以这个字母开头的最长的“龙”(每个单词都最多在“龙”中出现两次),在两个单词相连时,其重合部分合为一部分,例如 beast 和 astonish,如果接成一条龙则变为 beastonish,另外相邻的两部分不能存在包含关系,例如 at 和 atide 间不能相连。

输入格式

输入的第一行为一个单独的整数 n 表示单词数,以下 n 行每行有一个单词,输入的最后一行为一个单个字符,表示“龙”开头的字母。你可以假定以此字母开头的“龙”一定存在。

输出格式

只需输出以此字母开头的最长的“龙”的长度。

输入输出样例

输入 

5
at
touch
cheat
choose
tact
a

输出

23

说明/提示

样例解释:连成的“龙”为 atoucheatactactouchoose

n≤20

思路

遍历,深搜和递归。

分析题目,拆分一下要求。

最主要的是判断两个单词的最小覆盖部分,并且将覆盖后的结果继续遍历。那么很容易可以想到递归,通过递归来进行最小覆盖的连续判断,即在一个单词和另一个单词的覆盖判断成功后保留结果,并且进行深搜。

继续往下,结果所要的是返回最长的字符串,且要求同一个单词最多出现两次,那么就需要一个用来判断覆盖部分和总的字符串长度的函数。

那么步骤如下:

1.首先创建一个函数用来判断两个单词间是否存在接龙关系,并且返回接龙过程中重合的字符数,

代码如下:

int overlap_judge(int a, int b)//判断重叠部分的长度
{
	int yc=0;
	bool pk=true;
	for (int ks = word[a].size() - 1; ks >= 0; ks--)//从前一个单词的最后开始遍历
	{
		for (int k = ks; k < word[a].size(); k++)
		{
			if (word[a][k] != word[b][yc++])//把前一个单词的结尾和后一个单词的开头一一对照,一旦有不符合的部分就结束后面的对照
			{
				pk = false;
				break;
			}
		}
		yc = 0;//让对照关系重新回到后一个单词的开头
		if (pk == true)
			return word[a].size() - ks;//一旦存在未被打断的接龙关系,就返回重合的字符数
		pk = true;
	}
	return 0;//如果前一个单词遍历完还没有结果,则输出0
}

2.接着在判断重合字符的函数前提下,进行深搜,即判断接龙能够形成的最长字符串,

代码如下:

void scan_overlap(int p,int n)//参数为尾单词的序号和总单词个数
{
	flag = false;//判断递归中止的标记
	for (int j = 0; j < n; j++)
	{
		if (vis[j] >= 2)continue;//一个单词出现两次则跳过本次循环
		if (overlap_judge(p, j) == word[p].size() || overlap_judge(p,j)==word[j].size())continue;//包含关系跳过本次循环
		if (overlap_judge(p, j) == 0)continue;//不存在接龙关系
		flag = true;//确认此时的两个单词可以接龙
		an += word[j].size() - overlap_judge(p, j);//单词长度计算
		vis[j]++;//单词的使用次数加一
		scan_overlap(j, n);//继续接龙
		an -= word[j].size() - overlap_judge(p, j);//深搜的节点回归
		vis[j]--;
	}
	if (flag == false)
		ans = max(an, ans);//更新目前最长单词长度
	return;
}

3.主函数,输入和输出,还有根据输入的字母寻找单词为接龙的“龙头”

代码如下:

int main()
{
	int n;
	cin >> n;
	for (int i = 0; i < n; i++)
	{
		cin >> word[i];
	}
	char begin_word;
	cin >> begin_word;
	for (int i = 0; i < n; i++)
		for (int k = 0; k < n; k++)
			mc[i][k] = overlap_judge(i, k);//预处理,可以省略
	for (int i = 0; i < n; i++)
	{
		if (word[i][0] == begin_word)//根据给出的首字母找单词
		{
			an = word[i].size();//更新字符长度
			vis[i]++;//单词使用次数加一
			scan_overlap(i, n);//深搜
			vis[i] = 0;//清空使用次数,为了对首字母相同的不同单词为“龙头”的情况进行判断
		}
	}
	cout << ans;
	return 0;
}

总代码:

#include<iostream>
#include<string>
#include<cmath>
using namespace std;
string word[20];
bool flag;
int mc[20][20];
int vis[20];
int ans =-1;
int an = 0;


int overlap_judge(int a, int b)//判断重叠部分的长度
{
	int yc=0;
	bool pk=true;
	for (int ks = word[a].size() - 1; ks >= 0; ks--)
	{
		for (int k = ks; k < word[a].size(); k++)
		{
			if (word[a][k] != word[b][yc++])
			{
				pk = false;
				break;
			}
		}
		yc = 0;
		if (pk == true)
			return word[a].size() - ks;
		pk = true;
	}
	return 0;
}
void scan_overlap(int p,int n)//参数为尾单词的序号和总单词个数
{
	flag = false;//判断递归中止的标记
	for (int j = 0; j < n; j++)
	{
		if (vis[j] >= 2)continue;
		if (overlap_judge(p, j) == word[p].size() || overlap_judge(p,j)==word[j].size())continue;
		if (overlap_judge(p, j) == 0)continue;
		flag = true;
		an += word[j].size() - overlap_judge(p, j);
		vis[j]++;
		scan_overlap(j, n);
		an -= word[j].size() - overlap_judge(p, j);
		vis[j]--;
	}
	if (flag == false)
		ans = max(an, ans);
	return;
}
int main()
{
	int n;
	cin >> n;
	for (int i = 0; i < n; i++)
	{
		cin >> word[i];
	}
	char begin_word;
	cin >> begin_word;
	for (int i = 0; i < n; i++)
		for (int k = 0; k < n; k++)
			mc[i][k] = overlap_judge(i, k);
	for (int i = 0; i < n; i++)
	{
		if (word[i][0] == begin_word)
		{
			an = word[i].size();
			vis[i]++;
			scan_overlap(i, n);
			vis[i] = 0;
		}
	}
	cout << ans;
	return 0;
}
	

个人的注意点:

在判断条件中,“||”的优先级是高于“==”的即

if(overlap_judge(p,j)==word[p].size() || word[j].size())

if(overlap_judge(p,j)==word[p].size() || overlap_judge(p,j)==word[j].size())

的结果是不一样的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值