• AcWing 1117. 单词接龙


    \(AcWing\) \(1117\). 单词接龙

    题目传送门

    一、题目大意

    单词接龙是一个与我们经常玩的成语接龙相类似的游戏。

    现在我们已知一组单词,且给定一个开头的字母,要求出以这个字母开头的最长的“龙”,每个单词最多被使用两次

    在两个单词相连时,其重合部分合为一部分,例如 \(beast\)\(astonish\) ,如果接成一条龙则变为 \(beastonish\)

    我们可以任意选择重合部分的长度,但其长度必须大于等于\(1\),且 严格小于两个串的长度 ,例如 \(at\)\(atide\) 间不能相连。

    输入格式
    输入的第一行为一个单独的整数 \(n\) 表示单词数,以下 \(n\) 行每行有一个单词(只含有大写或小写字母,长度不超过\(20\)),输入的最后一行为一个单个字符,表示 开头的字母。

    你可以假定以此字母开头的 一定存在。

    输出格式
    只需输出以此字母开头的最长的 的长度。

    二、解题思路

    先来思考正常思路怎么写代码,再来思考能不能进化:

    • 遍历所有单词,找出 开头字母给定开始字母 的单词,它们都有可能是第一个单词

    • 假设选择的第一个单词是\(A\),那么需要逐个考查所有单词(也包括\(A\)自己),是不是可以拼接在当前单词\(A\)的后面,也就是 开头 与 结尾 存在 相同的子串。如果存在不同长度的子串,比如\(abdcd\)\(dcdab\),那么我们需要选择最短的子串,因为这样才能保证最后拼接的长度最长(贪心

    • 我们需要记录 每个单词 的 出现次数,因为题目要求最多不能超过两次

    • 由于每个单词都需要出现两次,也就是\(A\)\(B\)需要重复匹配最小重合长度,这样有点浪费,如果我们把这段匹配重合的代码抽取出来,只匹配一次,并且把结果记录下来的话,可以节省计算量,也就是 预处理

    三、朴素\(dfs\)

    #include <bits/stdc++.h>
    
    using namespace std;
    
    const int N = 30;
    
    int n;
    string word[N];
    int cnt[N];
    int ans;
    
    int getSameLength(string a, string b) {
        for (int i = 1; i < min(a.size(), b.size()); i++)
            if (a.substr(a.size() - i, i) == b.substr(0, i))
                return i;
        return 0;
    }
    
    void dfs(string dragon, int last) {
        ans = max((int)dragon.size(), ans);
        cnt[last]++;
        for (int i = 0; i < n; i++) {
            int len = getSameLength(word[last], word[i]);
    
            if (len && cnt[i] < 2)
                dfs(dragon + word[i].substr(len), i);
        }
    
        cnt[last]--;
    }
    
    int main() {
        cin.tie(0), ios::sync_with_stdio(false);
    
        cin >> n;
        for (int i = 0; i < n; i++) cin >> word[i];
    
        char start;
        cin >> start;
    
        for (int i = 0; i < n; i++)
            if (word[i][0] == start)
                dfs(word[i], i);
    
        printf("%d\n", ans);
        return 0;
    }
    

    四、实现代码 【\(for\)循环中回溯+预处理】

    #include <bits/stdc++.h>
    
    using namespace std;
    
    const int N = 30;
    
    int n;          //可以拼接的单词个数
    string word[N]; //单词表
    int g[N][N];    //代表编号i的可以被j拼接  如i:asd,j:sdf,拼接长度为最小值g[i][j] = 2,i从0开始记位
    int cnt[N];     //编号为i的单词使用次数
    int ans;        //记录最长的龙长度
    
    // dragon:已经拼接出的 龙 字符串
    // last:最后一个参加拼接的单词是几号单词
    void dfs(string dragon, int last) {
        //更新答案
        ans = max((int)dragon.size(), ans); // dragon.size()为当前合并的长度
    
        for (int i = 0; i < n; i++)
            if (g[last][i] && cnt[i] < 2) {                  // 如果最后一个拼接单词与当前单词存在重合部分,并且,当前单词使用次数小于2次
                cnt[i]++;                                    //整一下试试
                dfs(dragon + word[i].substr(g[last][i]), i); //编号为last的可以被i拼接现在尾巴为i号
                cnt[i]--;                                    //回溯,还原现场
            }
    }
    
    int main() {
        //加快读入
        cin.tie(0), ios::sync_with_stdio(false);
    
        //用来拼接龙的单词
        cin >> n;
        for (int i = 0; i < n; i++) cin >> word[i];
    
        //首字母
        char start;
        cin >> start;
    
        //预处理得到g[i][j],描述:
        // (1)a串与b串是否能够接龙,g[a][b]>0表示可以接龙,(2)g[a][b]=0表示接不上
        // (2) g[a][b]>0时,值描述的是当a串与b串接龙时,中间重复的部分有多少个字符
        for (int i = 0; i < n; i++)
            for (int j = 0; j < n; j++) { //注意:a串与a串是可以连接的,不需要判断 i!=j
                string a = word[i], b = word[j];
                for (int k = 1; k < min(a.size(), b.size()); k++) //枚举中间重复的字符数量,注意两种母串不能完全被吸收,所以k不能取到 a.size()或b.size()
                    if (a.substr(a.size() - k, k) == b.substr(0, k)) {
                        g[i][j] = k;
                        break; //首次找到的就是最短的匹配字符块,需要break,如果继续,则龙就变短了,不划算,贪心
                    }
                //黄海原来以为这里如果发现不相等,就需要break,其实不是的,比如:a=touch,b=cheat,只检查了h和c是不相等的,但不影响拼接成toucheat!
            }
        //重点:
        //(1)预处理出字符串之间的二维关系表
        //(2)枚举出重复部分的长度,符合就停止,不符合就继续,因为后面长一点的子串可能重合
    
        //找合适的起点
        for (int i = 0; i < n; i++)
            if (word[i][0] == start) {
                cnt[i]++;        // i号单词用了一次,记录成本 
                dfs(word[i], i); //从word[i]开始遍历,i代表现在是第几个单词
                cnt[i]--;        // i号单词还原现场,我没有使用,我还有两次机会,你们先来
            }
    
        printf("%d\n", ans);
        return 0;
    }
    

    五、实现代码 【\(dfs\)主函数中回溯+预处理】

    #include <bits/stdc++.h>
    
    using namespace std;
    
    const int N = 30;
    
    int n;
    string word[N];
    int g[N][N];
    int cnt[N];
    int ans;
    
    void dfs(string dragon, int last) {
        ans = max((int)dragon.size(), ans);
        cnt[last]++;
        for (int i = 0; i < n; i++)
            if (g[last][i] && cnt[i] < 2)
                dfs(dragon + word[i].substr(g[last][i]), i);
        cnt[last]--;
    }
    
    int main() {
        cin.tie(0), ios::sync_with_stdio(false);
    
        cin >> n;
        for (int i = 0; i < n; i++) cin >> word[i];
    
        char start;
        cin >> start;
    
        for (int i = 0; i < n; i++)
            for (int j = 0; j < n; j++) {
                string a = word[i], b = word[j];
                for (int k = 1; k < min(a.size(), b.size()); k++)
                    if (a.substr(a.size() - k, k) == b.substr(0, k)) {
                        g[i][j] = k;
                        break;
                    }
            }
    
        for (int i = 0; i < n; i++)
            if (word[i][0] == start)
                dfs(word[i], i);
    
        printf("%d\n", ans);
        return 0;
    }
    
  • 相关阅读:
    2019春第三次课程设计实验报告
    2019春第二次课程设计实验报告
    2019春第一次课程设计实验报告
    第十二周总结
    第十一周总结
    第五周课程总结&试验报告(三)
    第四周课程总结&实验报告(二)
    第三周课程总结&实验报告一
    第二周学习总结
    19春总结
  • 原文地址:https://www.cnblogs.com/littlehb/p/15976171.html
Copyright © 2020-2023  润新知