题目背景
注意:本题为上古 NOIP 原题,不保证存在靠谱的做法能通过该数据范围下的所有数据。
题目描述
单词接龙是一个与我们经常玩的成语接龙相类似的游戏,现在我们已知一组单词,且给定一个开头的字母,要求出以这个字母开头的最长的“龙”(每个单词都最多在“龙”中出现两次),在两个单词相连时,其重合部分合为一部分,例如 beast
和 astonish
,如果接成一条龙则变为 beastonish
,另外相邻的两部分不能存在包含关系,例如 at
和 atide
间不能相连。
输入格式
输入的第一行为一个单独的整数 nn 表示单词数,以下 nn 行每行有一个单词,输入的最后一行为一个单个字符,表示“龙”开头的字母。你可以假定以此字母开头的“龙”一定存在。
输出格式
只需输出以此字母开头的最长的“龙”的长度。
输入输出样例
输入:
5
at
touch
cheat
choose
tact
a
输出:23
说明/提示
样例解释:连成的“龙”为 atoucheatactactouchoose
。
n≤20。
我们主要的问题有:
1、如何判断两个字符串是否可以连接在一起。
2、如何判断包含关系(后面我们会发现,一个函数可以同时实现上面两个问题)。
3、如何记录长度。
我们将其分为两个函数实现:
1、 fun函数: (功能:是否可以连接,以及返回重合长度)
代码如下(示例):
//看重叠部分为多少
int fun(string s1,string s2){
int sLen1=s1.size() ,//存串长
sLen2=s2.size() ,
overlap=0; //重叠部分的长度
/*i标志s1的串尾位置,j标志s2的串头位置*/
for(int i=sLen1-1;i>0;i--){ //s1向前
int j=0;
if(s1[i]==s2[0]){ //当找到了连接处,在s1的i位置
for(int k=i;k<sLen1;k++,j++){ //在连接处k、j向后移动(k为s1的位置,j为s2的位置)
if(s1[k]==s2[j]) overlap++; //相同就将重叠部分长度+1
else { //否则跳出本轮循环
overlap=0;
break;
}
}
break;
//此处的break不能忘记,一开始我就是忘记跳出,导致结果错误
}
}
if(overlap==sLen1 || overlap==sLen2) return 0;//如果重叠部分等于其中一个串长,那么就是包含关系
return overlap;
}
2、 backtracking函数:(回溯:可参考DFS算法)
代码如下(示例):
//回溯法
void backtracking(int x){
for(int i=0;i<n;i++){
int overlap=fun(s[x].word,s[i].word);
if(s[i].useNumber<2 && overlap) //使用次数小于2 且 有重叠
{
cout<<s[i].word<<endl;
s[i].useNumber++; //次数+1
longer += (s[i].word.size() - overlap);
backtracking(i);
s[i].useNumber--;
longer -= (s[i].word.size() - overlap);
}
}
longest=max(longer,longest);
}
全部代码:
代码如下(示例):
#include <bits/stdc++.h>
#include <string>
#define Max 30
using namespace std;
int n; //单词个数
char headWord; //龙头
int longest=0; //最长龙
int longer=0;
struct words{ //结构体:包含单词和使用次数
string word;
int useNumber;
}s[Max];
void backtracking(int i);
int fun(string s1,string s2);
int main() {
cout<<fun("envelope","envelope");
cin>>n;
for(int i=0;i<n;i++) {
cin>>s[i].word;
s[i].useNumber=0;
}
cin>>headWord;
for(int i=0;i<n;i++){
longer=0;
if(headWord == s[i].word[0])
{
s[i].useNumber++;
longer += s[i].word.size();
backtracking(i);
s[i].useNumber--;
longer -= s[i].word.size();
}
}
cout<<longest;
}
//回溯法
void backtracking(int x){
for(int i=0;i<n;i++){
int overlap=fun(s[x].word,s[i].word);
if(s[i].useNumber<2 && overlap) //使用次数小于2 且 有重叠
{
cout<<s[i].word<<endl;
s[i].useNumber++; //次数+1
longer += (s[i].word.size() - overlap);
backtracking(i);
s[i].useNumber--;
longer -= (s[i].word.size() - overlap);
}
}
longest=max(longer,longest);
}
//看重叠部分为多少
int fun(string s1,string s2){
int sLen1=s1.size() ,//存串长
sLen2=s2.size() ,
overlap=0; //重叠部分的长度
/*i标志s1的串尾位置,j标志s2的串头位置*/
for(int i=sLen1-1;i>0;i--){ //s1向前
int j=0;
if(s1[i]==s2[0]){ //当找到了连接处,在s1的i位置
for(int k=i;k<sLen1;k++,j++){ //在连接处k、j向后移动(k为s1的位置,j为s2的位置)
if(s1[k]==s2[j]) overlap++; //相同就将重叠部分长度+1
else { //否则跳出本轮循环
overlap=0;
break;
}
}
break;
}
}
if(overlap==sLen1 || overlap==sLen2) return 0;//如果重叠部分等于其中一个串长,那么就是包含关系
return overlap;
}
你悟解了吗?