#include
#include
#include
using namespace std;
#define MAXN 21
#define MAXLENGTH 21
int n;
int length,max_length;
char words[MAXN][MAXLENGTH];
char current_word[1000];
int book[MAXN];
inline void add(int order,int start)//进行单词连接
{
book[order]++;
int len=strlen(words[order]);
while(start<len)
{
current_word[length]=words[order][start];
length++;
start++;
}
current_word[length]='\0';
}
inline int match(int order,int start)//检查是否匹配
{
//cout<<"match"<
int t=0;
int i;
int len;
for(i=start;i<length;i++,t++)
{
if(current_word[i]!=words[order][t])
{
return 0;
}
}
len=length-start;
return len;
}
inline int check(int order,int word_length)//check
{
int flag=0;
int start;
for(int i=length-1;i>=length+1-word_length;i--)
{
if(current_word[i]==words[order][0])
{
start=match(order,i);
if(start)
{
add(order,start);
flag=1;
return flag;
}
}
}
return flag;
}
inline void dfs()//普通的dfs啊
{
char word[1000];
int len1;
strcpy(word,current_word);
len1=strlen(current_word);
int len;
if(length>max_length)
{
max_length=length;
}
for(int i=0;i<n;i++)
{
if(book[i]==2)
{
continue;
}
len=strlen(words[i]);
if(!check(i,len))
{
continue;
}
dfs();
book[i]--;
strcpy(current_word,word);
length=len1;
}
return;
}
int main()
{
char origin_letter;
cin>>n;
for(int i=0;i<n;i++)
{
cin>>words[i];
}
cin>>origin_letter;
for(int i=0;i<n;i++)
{
if(words[i][0]!=origin_letter)
{
continue;
}
memset(book,0,sizeof(book));
length=0;
strcpy(current_word,words[i]);
length=strlen(words[i]);
book[i]++;
if(length>max_length)
{
max_length=length;
}
dfs();
}
cout<<max_length;
return 0;
}
自己的思路:看到题目分类是dfs,自然采取dfs作为核心思路,但是对比公共题解,发现可以采取更好的办法,即采取预处理的方法,预处理思路如下:
1.'龙’的每个部分都是由单词连接而成,那么其实可以拆分成两两单词连接.
2.所以提前判断两个单词之间是否可以进行连接,用数组存下前单词与后单词之间的叠数,之后操作就变得简单.
附上洛谷题解代码:
#include
#include
#include
#include
using namespace std;
int n;//单词数
string tr[30];//存储字符串
int yc[30][30];//两个字母的最小重叠部分
int vis[30];//判断单词使用频率.
int mt(int x, int y){//mt函数,返回x单词后连接一个y单词的最小重叠部分
bool pp=true;
int ky=0;
for(int k=tr[x].size()-1;k>=0;k--){//从x单词尾部向前看看最小重叠部分是从哪里开始的,以为因为是倒着来,所以保证是最小的
for(int kx=k;kx<tr[x].size();kx++){
if(tr[x][kx]!=tr[y][ky++]){
pp=false;
break;
}
}
if(pp==true){//如果说当前以k为开头的前一个单词后缀 ,是后面单词的前缀,就马上返回重叠部分。(tr[x].size()-k是找出来的规律)
return tr[x].size()-k; }
ky=0;
pp=true;//不行就继续
}
return 0;
}//可能这里有点难理解。可以手动模拟一下
char ch;//开头字母
int ans=-1;//答案
int an=0;//每次搜到的当前最长串
void dfs(int p){//p为尾部单词编号(p的后缀就是“龙”的后缀,因为p已经连接到”龙“后面了)
bool jx=false;
for(int j=1;j<=n;j++){
if(vis[j]>=2) continue;//使用了两次就跳过
if(yc[p][j]==0) continue;//两单词之间没有重合部分就跳过
if(yc[p][j]==tr[p].size() || yc[p][j]==tr[j].size()) continue;//两者存在包含关系就跳过
an+=tr[j].size()-yc[p][j];//两单词合并再减去最小重合部分
vis[j]++;//使用了一次
jx=true;//标记一下当前已经成功匹配到一个可以连接的部分
dfs(j); //接上去
an-=tr[j].size()-yc[p][j];//回溯,就要再减回去那一部分长度
vis[j]--;//回溯,使用--
}
if(jx==false){//jx==false说明不能再找到任何一个单词可以相连了
ans=max(ans,an);//更新ans
}
return;
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++)
cin>>tr[i];
cin>>ch;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
yc[i][j]=mt(i,j);
}
}//预处理yc数组。yc[i][j]就表示,i单词后连接一个j单词的最小重叠部分
//比如 i表示at,j表示att. yc[i][j]就为2 但是yc[j][i]就为0.
//预处理是一个关键
for(int i=1;i<=n;i++){//从头到尾看一下有没有以指定开头字母为开头的单词
if(tr[i][0]==ch){//如果有,就以当前单词为基准进行搜索。
vis[i]++;//使用过一次
an=tr[i].size();//更新当前串长度
dfs(i);//接上
vis[i]=0;//消除影响
}
}
printf("%d",ans);
return 0;
}
相比起来,自己的代码采取的是边选取边判断的思路,明显使代码变得冗余,所以预处理这里是一个优化的点.
这里有个隐性的问题需要提到,比如单词A为"abcd",单词B为"defgh",单词c为abcdefghijk,如果仅是两两单词之间判断是否可以连接的话,那么明显可以采取的方式只有AB,AC,但是如果’龙’是AB,即"abcdefgh",那么这时候C可以连在龙尾,即ABC,而如果二二单词连接而成的话,BC是不允许的,但是考虑到采取ABC连接和AC连接,最后的效果是一样的,而且AC连接还可以省出一个B,可能可以为后面的’龙’添加长度,所以AC更加,这在无形中使得二二连接方式直接省略这一步的考虑,因洛谷题解未提出,因防止有人有相同疑问,故在此讲解
此外,说一下题解二,先附上代码
#include
using namespace std;
string str[20];
int use[20], length = 0, n;
int canlink(string str1, string str2) {
for(int i = 1; i < min(str1.length(), str2.length()); i++) {//重叠长度从1开始,直到最短的字符串长度-1(因为不能包含)
int flag = 1;
for(int j = 0; j < i; j++)
if(str1[str1.length() - i + j] != str2[j]) flag = 0;//逐个检测是否相等
if(flag) return i;//检测完毕相等则立即return
}
return 0;//无重叠部分,返回0
}
void solve(string strnow, int lengthnow) {
length = max(lengthnow, length);//更新最大长度
for(int i = 0; i < n; i++) {
if(use[i] >= 2) continue;//该字符串使用次数需要小于2
int c = canlink(strnow, str[i]);//获取重叠长度
if(c > 0) {//有重叠部分就开始dfs
use[i]++;
solve(str[i], lengthnow + str[i].length() - c);
use[i]--;
}
}
}
main() {
cin >> n;
for(int i = 0; i <= n; i++) use[i] = 0, cin >> str[i];//str[n]为开始字符
solve(' '+str[n], 1);//有必要解释一下开始阶段。为了指定第一个字符,而且因为canlink需要重叠部分小于最短长度-1,所以要从前面添加一个无意义充长度的‘ ’。这样就强制了canlink函数比较最后一位。
cout << length ;
}
对于题解二,第一眼看到就很惊艳,自己代码长的一批,对题解中答主注释的追求简洁的态度很是认同,同时此题采用的也是预处理的操作,然后就是学到的几点:
在这里提一嘴,倒数第三行中的’ ‘就像答主说的因为canlink函数中的重叠长度为最小长度减一,所以这里得放一个’ ‘来占长度,但是我觉得这个操作mmm,跟题解评论一样,显得可读性太差,不如加一个判断来得到’龙头’,个人意见,不喜勿喷
后面题解思路接近,END.