洛谷-DFS-1019-单词接龙-个人AC题解和公共AC题解笔记

学习内容:

  • 预处理
  • 万能头文件
  • string的使用


    话不多说,直奔主题

本人AC代码

#include<iostream>
#include<cstdio>
#include<cstring>
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"<<endl;
    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<cstdio>
#include<iostream>
#include<string>
#include<cmath> 
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<bits/stdc++.h>
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 ;
}

对于题解二,第一眼看到就很惊艳,自己代码长的一批,对题解中答主注释的追求简洁的态度很是认同,同时此题采用的也是预处理的操作,然后就是学到的几点:

  • 万能头文件:bits/stdc++.h
  • string数据类型的一些使用
    • 方法:length()
    • 和其他语言类似的’+'来连接string

在这里提一嘴,倒数第三行中的’ ‘就像答主说的因为canlink函数中的重叠长度为最小长度减一,所以这里得放一个’ ‘来占长度,但是我觉得这个操作mmm,跟题解评论一样,显得可读性太差,不如加一个判断来得到’龙头’,个人意见,不喜勿喷

后面题解思路接近,END.

 
 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值