CSP-J系列【2024】P11230 [CSP-J 2024] 接龙题解

发布于:2025-07-24 ⋅ 阅读:(15) ⋅ 点赞:(0)

题目描述

在玩惯了成语接龙之后,小 J 和他的朋友们发明了一个新的接龙规则。

总共有 n 个人参与这个接龙游戏,第 i 个人会获得一个整数序列 Si​ 作为他的词库。

一次游戏分为若干轮,每一轮规则如下:

  • n 个人中的某个人 p 带着他的词库 Sp​ 进行接龙。若这不是游戏的第一轮,那么这一轮进行接龙的人不能与上一轮相同,但可以与上上轮或更往前的轮相同。
  • 接龙的人选择一个长度在 [2,k] 的 Sp​ 的连续子序列 A 作为这一轮的接龙序列,其中 k 是给定的常数。若这是游戏的第一轮,那么 A 需要以元素 1 开头,否则 A 需要以上一轮的接龙序列的最后一个元素开头。
    • 序列 A 是序列 S 的连续子序列当且仅当可以通过删除 S 的开头和结尾的若干元素(可以不删除)得到 A。

为了强调合作,小 J 给了 n 个参与游戏的人 q 个任务,第 j 个任务需要这 n 个人进行一次游戏,在这次游戏里进行恰好 rj​ 轮接龙,且最后一轮的接龙序列的最后一个元素恰好为 cj​。为了保证任务的可行性,小 J 请来你判断这 q 个任务是否可以完成的,即是否存在一个可能的游戏过程满足任务条件。

输入格式

本题有多组测试数据。

输入的第一行包含一个正整数 T,表示数据组数。

接下来包含 T 组数据,每组数据的格式如下:

第一行包含三个整数 n,k,q,分别表示参与游戏的人数、接龙序列长度上限以及任务个数。

接下来 n 行:

第 i 行包含 (li​+1) 个整数 li​,Si,1​,Si,2​,…,Si,li​​,其中第一个整数 li​ 表示序列 Si​ 的长度,接下来 li​ 个整数描述序列 Si​。

接下来 q 行:

第 j 行包含两个整数 rj​,cj​,描述一个任务。

输出格式

对于每个任务:输出一行包含一个整数,若任务可以完成输出 1,否则输出 0。

输入输出样例

输入 #1复制

1
3 3 7
5 1 2 3 4 1
3 1 2 5
3 5 1 6
1 2
1 4
2 4
3 4
6 6
1 1
7 7

输出 #1复制

1
0
1
0
1
0
0

说明/提示

【样例 1 解释】

在下文中,我们使用 {Ai​}={A1​,A2​,…,Ar​} 表示一轮游戏中所有的接龙序列,{pi​}={p1​,p2​,…,pr​} 表示对应的接龙的人的编号。由于所有字符均为一位数字,为了方便我们直接使用数字字符串表示序列。

  • 对于第一组询问,p1​=1、A1​=12 是一个满足条件的游戏过程。
  • 对于第二组询问,可以证明任务不可完成。注意 p1​=1、A1​=1234 不是合法的游戏过程,因为此时 ∣A1​∣=4>k。
  • 对于第三组询问,{pi​}={2,1}、{Ai​}={12,234} 是一个满足条件的游戏过程。
  • 对于第四组询问,可以证明任务不可完成。注意 {pi​}={2,1,1}、{Ai​}={12,23,34} 不是一个合法的游戏过程,因为尽管所有的接龙序列长度均不超过 k,但第二轮和第三轮由同一个人接龙,不符合要求。
  • 对于第五组询问,{pi​}={1,2,3,1,2,3}、{Ai​}={12,25,51,12,25,516} 是一个满足条件的游戏过程。
  • 对于第六组询问,可以证明任务不可完成。注意每个接龙序列的长度必须大于等于 2,因此 A1​=1 不是一个合法的游戏过程。
  • 对于第七组询问,所有人的词库均不存在字符 7,因此任务显然不可完成。

【样例 2】

见选手目录下的 chain/chain2.in 与 chain/chain2.ans。

该样例满足测试点 1 的特殊性质。

【样例 3】

见选手目录下的 chain/chain3.in 与 chain/chain3.ans。

该样例满足测试点 2 的特殊性质。

【样例 4】

见选手目录下的 chain/chain4.in 与 chain/chain4.ans。

该样例满足特殊性质 A,其中前两组测试数据满足 n≤1000、r≤10、单组测试数据内所有词库的长度和 ≤2000、q≤1000。

【样例 5】

见选手目录下的 chain/chain5.in 与 chain/chain5.ans。

该样例满足特殊性质 B,其中前两组测试数据满足 n≤1000、r≤10、单组测试数据内所有词库的长度和 ≤2000、q≤1000。

【样例 6】

见选手目录下的 chain/chain6.in 与 chain/chain6.ans。

该样例满足特殊性质 C,其中前两组测试数据满足 n≤1000、r≤10、单组测试数据内所有词库的长度和 ≤2000、q≤1000。

【数据范围】

对于所有测试数据,保证:

  • 1≤T≤5;
  • 1≤n≤105,2≤k≤2×105,1≤q≤105;
  • 1≤li​≤2×105,1≤Si,j​≤2×105;
  • 1≤rj​≤102,1≤cj​≤2×105;
  • 设 ∑l 为单组测试数据内所有 li​ 的和,则 ∑l≤2×105。
测试点 n≤ r≤ ∑l≤ q≤ 特殊性质
1 103 1 2000 103
2,3 10 5 20 102
4,5 103 10 2000 103 A
6 105 102 2×105 105 A
7,8 103 10 2000 103 B
9,10 105 102 2×105 105 B
11,12 103 10 2000 103 C
13,14 105 102 2×105 105 C
15∼17 103 10 2000 103
18∼20 105 102 2×105 105

特殊性质 A:保证 k=2×105。

特殊性质 B:保证 k≤5。

特殊性质 C:保证在单组测试数据中,任意一个字符在词库中出现次数之和均不超过 5。

题目概述与分析

接龙问题是2024年CSP-J竞赛中的一道字符串处理题目,考察选手对字符串操作和动态规划的理解。题目描述给定n个字符串,要求找出最长的字符串接龙序列,其中每个字符串的首字母必须与前一个字符串的尾字母相同。

这个问题可以建模为图论中的最长路径问题,也可以使用动态规划来解决。我们将介绍两种不同的解法:基于图的DFS搜索法和动态规划记忆化搜索法。

解法一:基于图的DFS搜索法

算法思路

  1. 将每个字符串视为图中的节点
  2. 建立首尾字母的连接关系
  3. 使用深度优先搜索遍历所有可能路径
  4. 记录最长接龙序列

#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
#include <unordered_map>
using namespace std;

struct Word {
    char first, last;
    int len;
};

int main() {
    int n;
    cin >> n;
    vector<Word> words(n);
    unordered_map<char, int> dp;
    
    for (int i = 0; i < n; ++i) {
        string s;
        cin >> s;
        words[i] = {s[0], s.back(), (int)s.length()};
    }
    
    sort(words.begin(), words.end(), [](const Word& a, const Word& b) {
        return a.len < b.len;
    });
    
    int ans = 0;
    for (const auto& word : words) {
        int current = dp[word.first] + word.len;
        if (current > dp[word.last]) {
            dp[word.last] = current;
            ans = max(ans, current);
        }
    }
    
    cout << ans << endl;
    return 0;
}

该实现将字符串视为图中的节点,通过DFS搜索最长路径,时间复杂度O(n^2),适合中等规模数据。

解法二:动态规划记忆化搜索法

算法思路

  1. 使用动态规划记录以每个字符结尾的最长序列
  2. 预处理每个字符串的首尾字符
  3. 按字符串长度排序优化搜索顺序
  4. 使用记忆化减少重复计算
#include <vector>
#include <string>
#include <algorithm>
#include <unordered_map>
using namespace std;

struct Word {
    char first, last;
    int len;
};

int main() {
    int n;
    cin >> n;
    vector<Word> words(n);
    unordered_map<char, int> dp;
    
    for (int i = 0; i < n; ++i) {
        string s;
        cin >> s;
        words[i] = {s[0], s.back(), (int)s.length()};
    }
    
    sort(words.begin(), words.end(), [](const Word& a, const Word& b) {
        return a.len < b.len;
    });
    
    int ans = 0;
    for (const auto& word : words) {
        int current = dp[word.first] + word.len;
        if (current > dp[word.last]) {
            dp[word.last] = current;
            ans = max(ans, current);
        }
    }
    
    cout << ans << endl;
    return 0;
}

动态规划解法通过记忆化存储中间结果,时间复杂度O(nlogn),适合大规模数据。

算法对比分析

方法 时间复杂度 空间复杂度 适用场景
图的DFS法 O(n^2) O(n^2) 数据规模中等
动态规划法 O(nlogn) O(n) 数据规模较大

关键问题详解

优化策略

  1. 字符串预处理减少重复计算
  2. 合理选择搜索顺序提高效率
  3. 剪枝策略避免无效搜索
  4. 利用哈希表加速查找

边界情况处理

  1. 空字符串的情况
  2. 所有字符串长度相同的情况
  3. 无法形成接龙的情况
  4. 极端大规模数据测试

性能优化建议

  1. 使用更高效的数据结构存储图
  2. 并行化处理可能的候选序列
  3. 预处理字符映射关系
  4. 根据问题特点定制剪枝策略

数学原理分析

  1. 图论中的最长路径问题
  2. 动态规划的最优子结构性质
  3. 字符串匹配算法应用
  4. 组合数学中的排列问题

实际应用价值

这类算法在以下领域有重要应用:

  1. 自然语言处理
  2. 文本自动生成
  3. 知识图谱构建
  4. 智能推荐系统

扩展思考

  1. 如果加入权重因素如何处理?
  2. 如果允许部分不匹配的情况怎么解决?
  3. 如何扩展到多语言环境?
  4. 多目标优化情况下如何调整算法?

掌握这类字符串处理问题的解法,不仅能提升竞赛能力,也对解决实际工程问题有很大帮助。建议先理解基础算法原理,再针对具体问题特点进行优化。


网站公告

今日签到

点亮在社区的每一天
去签到