P1019 [NOIP 2000 提高组] 单词接龙(深度搜索)

题目背景

注意:本题为上古 NOIP 原题,不保证存在靠谱的做法能通过该数据范围下的所有数据。

NOIP2000 提高组 T3

题目描述

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

输入格式

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

输出格式

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

输入输出样例

输入

5
at
touch
cheat
choose
tact
a

输出

23

说明/提示

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

n≤20。

代码:

#include <bits/stdc++.h>
using namespace std;

const int N = 30;           // 最大单词数量上限
int n;                      // 单词的实际数量
string words[N];            // 存储输入的单词数组
int used[N];                // 记录每个单词被使用的次数(每个单词最多使用两次)
int g[N][N];                // 邻接矩阵:g[i][j] 表示单词 i 的后缀与单词 j 的前缀重合的长度
int res = 0;                // 记录“龙”的最大长度(最终答案)

// 深度优先搜索函数
// 参数:dragon 表示当前构造的“龙”字符串;x 表示上一个使用的单词的索引
void dfs(string dragon, int x) {
    used[x]++;  // 将当前单词 x 的使用次数加 1
    // 更新全局结果:取当前“龙”字符串的长度与 res 的较大值
    res = max(res, (int)dragon.size());
    
    // 遍历所有单词,尝试接在当前单词后面
    for (int i = 0; i < n; i++) {
        // 如果单词 x 和单词 i 有重合部分(g[x][i] > 0)
        // 且单词 i 的使用次数少于 2,则可以尝试接上单词 i
        if (g[x][i] && used[i] < 2) {
            // 拼接新字符串:将当前字符串 dragon 后接单词 i 去除重合部分后的子串
            dfs(dragon + words[i].substr(g[x][i]), i);
        }
    }
    used[x]--;  // 回溯:撤销单词 x 的使用标记
}

int main() {
    cin >> n;  // 输入单词数量
    for (int i = 0; i < n; i++) {
        cin >> words[i];  // 输入每个单词
    }
    char start;
    cin >> start;  // 输入“龙”起始的字母

    // 构造邻接矩阵 g[i][j]
    // 对于任意两个单词 words[i] 和 words[j],
    // 尝试找出 words[i] 的后缀与 words[j] 的前缀重合的最短部分
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
            string a = words[i], b = words[j];
            // k 表示可能的重合长度,从 1 到 min(a.size(), b.size()) - 可能的重合长度
            for (int k = 1; k < min(a.size(), b.size()); k++) {
                // 如果 words[i] 的后 k 个字符等于 words[j] 的前 k 个字符
                if (a.substr(a.size() - k, k) == b.substr(0, k)) {
                    g[i][j] = k;  // 记录重合长度
                    break;        // 找到第一个重合部分后退出循环(题目要求重合部分越短越好)
                }
            }
        }
    }

    // 对所有以 start 字母开头的单词作为起点开始 DFS
    for (int i = 0; i < n; i++) {
        if (words[i][0] == start) {
            dfs(words[i], i);
        }
    }
    
    // 输出最长“龙”的长度
    cout << res;
    return 0;
}

解题步骤和思路说明

  1. 题目目标:
    给定一组单词和一个起始字母,要求将单词通过接龙的方式(允许每个单词最多使用两次)拼接成一条“龙”。接龙时,当两个单词相连,它们的重合部分只保留一份(例如 beastastonish 接成 beastonish),且相邻两部分之间不能存在包含关系。最后输出拼接得到的字符串的最大长度。

  2. 预处理阶段:构造邻接矩阵

    • 对于每对单词

      words[i]
      

      words[j]
      

      ,尝试计算它们之间的重合长度,即:

      • k 从 1 到 min(a.size(), b.size()) - 1(注意题目中要求“重合的部分越短越好”,因此找到第一个匹配就停止),
      • 如果 words[i] 的后 k 个字符与 words[j] 的前 k 个字符相同,则记录 g[i][j] = k,表示可以将单词 j 接在单词 i 后面时重合的字符数为 k。
    • 得到的矩阵 g 就是一个邻接矩阵,表示从单词 i 到单词 j 是否可以接龙以及接龙时应剪去的字符数。

  3. DFS 搜索阶段:构造“龙”

    • 定义 DFS 函数,参数包括当前构造的字符串 dragon 和最后一个使用的单词的索引 x
    • 在 DFS 中,首先将单词 x 的使用次数加 1,并更新全局结果 res 为当前字符串长度的最大值。
    • 然后遍历所有单词,检查是否可以接在当前单词后面:
      • 如果 g[x][i] 不为 0(即存在重合部分),且单词 i 的使用次数还少于 2,则可以递归调用 DFS,将单词 i 接到当前字符串后面(接龙时去掉单词 i 中与单词 x 重合的部分,即 words[i].substr(g[x][i]))。
    • 递归返回后,回溯时将单词 x 的使用次数减 1,确保其他路径中可以重新使用该单词(每个单词最多允许使用两次)。
  4. 起点选择与答案输出:

    • main 函数中,先读取所有单词和起始字母,然后利用邻接矩阵预处理所有单词之间的重合信息。
    • 接着,对所有首字母等于给定起始字母的单词作为起点,调用 DFS 开始搜索。
    • 搜索完成后,res 中保存的是拼接成的“龙”的最长长度,最后将 res 输出。
  5. 注意点:

    • 每个单词最多使用两次,所以在 DFS 中通过数组 used 控制每个单词的使用次数;
    • DFS 搜索过程中,字符串的拼接方式利用了 substr 来跳过重合部分,这里跳出循环,是因为要找最长的序列,从最小的重叠子串开始找的,找到了就退出循环,从而保证接龙后的字符串不会重复计算重合部分的字符;
    • 邻接矩阵 g 的构造中,只考虑找到最短的重合部分(即找到第一个匹配就跳出循环),这符合题目要求。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

week_泽

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值