2024 CSP-J 第二轮真题解析

文章目录

T1. 扑克牌

题目链接:SOJ P5142

小 P 从同学小 Q 那儿借来一副 n n n 张牌的扑克牌。

本题中我们不考虑大小王,此时每张牌具有两个属性:花色和点数。花色共有 4 4 4 种:方片、草花、红桃和黑桃。点数共有 13 13 13 种,从小到大分别为 A 2 3 4 5 6 7 8 9 T J Q K \tt A\ 2\ 3\ 4\ 5\ 6\ 7\ 8\ 9\ T\ J\ Q\ K A 2 3 4 5 6 7 8 9 T J Q K。注意:点数 10 10 10 在本题中记为 T \tt T T。

我们称一副扑克牌是完整 的,当且仅当对于每一种花色和每一种点数,都恰好 有一张牌具有对应的花色和点数。由此,一副完整的扑克牌恰好有 4 × 13 = 52 4 × 13 = 52 4×13=52 张牌。以下图片展示了一副完整的扑克牌里所有的 52 52 52 张牌。

小 P 借来的牌可能不是完整的,为此小 P 准备再向同学小 S 借若干张牌。可以认为小 S 每种牌都有无限张,因此小 P 可以任意选择借来的牌。小 P 想知道他至少得向小 S 借多少张牌,才能让从小 S 和小 Q 借来的牌中,可以选出 52 52 52 张牌构成一副完整的扑克牌。

为了方便你的输入,我们使用字符 D \tt D D 代表方片,字符 C \tt C C 代表草花,字符 H \tt H H 代表红桃,字符 S \tt S S 代表黑桃,这样每张牌可以通过一个长度为 2 2 2 的字符串表示,其中第一个字符表示这张牌的花色,第二个字符表示这张牌的点数,例如 C A \tt CA CA 表示草花 A \tt A A, S T \tt ST ST 表示黑桃 T \tt T T(黑桃 10 10 10)。

时间限制:1 s

内存限制:512 MB

  • 输入

    输入的第一行包含一个整数 n n n 表示牌数。

    接下来 n n n 行,每行包含一个长度为 2 2 2 的字符串描述一张牌,其中第一个字符描述其花色,第二个字符描述其点数。

  • 输出

    输出一行一个整数,表示最少还需要向小 S 借几张牌才能凑成一副完整的扑克牌。

  • 样例输入 1

    a 复制代码
    1
    SA
  • 样例输出 1

    a 复制代码
    51
  • 样例输入 2

    a 复制代码
    4
    DQ
    H3
    DQ
    DT
  • 样例输出 2

    a 复制代码
    49
  • 数据规模与约定

    对于所有测试数据,保证: 1 ≤ n ≤ 52 1 ≤ n ≤ 52 1≤n≤52,输入的 n n n 个字符串每个都代表一张合法的扑克牌,即字符串长度为 2 2 2,且第一个字符为 D C H S \tt D\ C\ H\ S D C H S 中的某个字符,第二个字符为 A 2 3 4 5 6 7 8 9 T J Q K \tt A\ 2\ 3\ 4\ 5\ 6\ 7\ 8\ 9\ T\ J\ Q\ K A 2 3 4 5 6 7 8 9 T J Q K 中的某个字符。

思路分析

此题考查哈希表 / 集合的去重应用,属于入门题。

一副完整的扑克牌共有 52 52 52 张互不相同的牌。小 Q 拿到了 n n n 张牌,其中可能存在重复的牌。

  1. 可以使用 C++ STL 中的 std::set<string> 存储所有输入的字符串(牌名),set 会自动过滤掉重复的元素。
  2. 读入完所有 n n n 张牌后,set 的大小 st.size() 即为小 Q 实际拥有的不同扑克牌数量。
  3. 所需补充的牌数即为: 52 − st.size() 52 - \text{st.size()} 52−st.size()。

算法的时间复杂度为 O ( n log ⁡ n ) O(n \log n) O(nlogn),空间复杂度为 O ( n ) O(n) O(n),对于 n ≤ 52 n \le 52 n≤52 可以在 1 ms 内运行完毕。

cpp 复制代码
/*
 * Name: T1.cpp
 * Problem: 扑克牌
 * Author: Teacher Gao.
 * Date&Time: 2026/09/29 19:45
 */

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

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);

    int n;
    cin >> n;

    set<string> st;
    for (int i = 0; i < n; i++) {
        string s;
        cin >> s;
        st.insert(s);
    }

    cout << 52 - st.size() << "\n";

    return 0;
}

T2. 地图探险

题目链接:SOJ P5143

小 A 打算前往一片丛林去探险。丛林的地理环境十分复杂,为了防止迷路,他先派遣了一个机器人前去探路。

丛林的地图可以用一个 n n n 行 m m m 列的字符表来表示。我们将第 i i i 行第 j j j 列的位置的坐标记作 ( i , j ) ( 1 ≤ i ≤ n , 1 ≤ j ≤ m ) (i, j)(1 ≤ i ≤ n, 1 ≤ j ≤ m) (i,j)(1≤i≤n,1≤j≤m)。如果这个位置的字符为 x,即代表这个位置上有障碍,不可通过。反之,若这个位置的字符为 .,即代表这个位置是一片空地,可以通过。

这个机器人的状态由位置和朝向两部分组成。其中位置由坐标 ( x , y ) ( 1 ≤ x ≤ n , 1 ≤ y ≤ m ) (x, y)(1 ≤ x ≤ n, 1 ≤ y ≤ m) (x,y)(1≤x≤n,1≤y≤m) 刻画,它表示机器人处在地图上第 x x x 行第 y y y 列的位置。而朝向用一个 0 ∼ 3 0 \sim 3 0∼3 的整数 d d d 表示,其中 d = 0 d = 0 d=0 代表向东, d = 1 d = 1 d=1 代表向南, d = 2 d = 2 d=2 代表向西, d = 3 d = 3 d=3 代表向北。

初始时,机器人的位置为 ( x 0 , y 0 ) (x_0, y_0) (x0,y0),朝向为 d 0 d_0 d0。保证初始时机器人所在的位置为空地 。接下来机器人将要进行 k k k 次操作。每一步,机器人将按照如下的模式操作:

  1. 假设机器人当前处在的位置为 ( x , y ) (x, y) (x,y),朝向为 d d d。则它的方向上的下一步的位置 ( x ′ , y ′ ) (x^′, y^′) (x′,y′) 定义如下:
    • 若 d = 0 d = 0 d=0,则令 ( x ′ , y ′ ) = ( x , y + 1 ) (x^′, y^′) = (x, y + 1) (x′,y′)=(x,y+1)
    • 若 d = 1 d = 1 d=1,则令 ( x ′ , y ′ ) = ( x + 1 , y ) (x^′, y^′) = (x + 1, y) (x′,y′)=(x+1,y)
    • 若 d = 2 d = 2 d=2,则令 ( x ′ , y ′ ) = ( x , y − 1 ) (x^′, y^′) = (x, y − 1) (x′,y′)=(x,y−1)
    • 若 d = 3 d = 3 d=3,则令 ( x ′ , y ′ ) = ( x − 1 , y ) (x^′, y^′) = (x − 1, y) (x′,y′)=(x−1,y)
  2. 接下来,机器人判断它下一步的位置是否在地图内,且是否为空地。具体地说,它判断 ( x ′ , y ′ ) (x^′, y^′) (x′,y′) 是否满足 1 ≤ x ′ ≤ n , 1 ≤ y ′ ≤ m 1 ≤ x^′ ≤ n, 1 ≤ y^′ ≤ m 1≤x′≤n,1≤y′≤m,且 ( x ′ , y ′ ) (x^′, y^′) (x′,y′) 位置上是空地。如果条件成立,则机器人会向前走一步。它新的位置变为 ( x ′ , y ′ ) (x^′, y^′) (x′,y′),且朝向不变。如果条件不成立,则它会执行 "向右转" 操作。也就是说,令 d ′ = ( d + 1 ) m o d    4 d^′ = (d + 1) \mod 4 d′=(d+1)mod4(即
    d + 1 d + 1 d+1 除以 4 4 4 的余数),且它所处的位置保持不变,但朝向由 d d d 变为 d ′ d^′ d′。

小 A 想要知道,在机器人执行完 k k k 步操作之后,地图上所有被机器人经过的位置(包括起始位置)有几个。

时间限制:1 s

内存限制:512 MB

  • 输入

    本题有多组测试数据 。

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

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

    第一行包含三个正整数 n , m , k n, m, k n,m,k。其中 n , m n,m n,m 表示地图的行数和列数, k k k 表示机器人执行操作的次数。

    第二行包含两个正整数 x 0 , y 0 x_0, y_0 x0,y0 和一个非负整数 d 0 d_0 d0。

    接下来 n n n 行,每行包含一个长度为 m m m 的字符串。保证字符串中只包含 x 和 . 两个字符。其中,第 x x x 行的字符串的第 y y y 个字符代表的位置为 ( x , y ) (x, y) (x,y)。这个位置是 x 即代表它是障碍,否则代表它是空地。数据保证机器人初始时所在的位置为空地。

  • 输出

    对于每组数据:输出一行包含一个正整数,表示地图上所有被机器人经过的位置(包括起始位置)的个数。

  • 样例输入

    a 复制代码
    2
    1 5 4
    1 1 2
    ....x
    5 5 20
    1 1 0
    .....
    .xxx.
    .x.x.
    ..xx.
    x....
  • 样例输出

    a 复制代码
    3
    13
  • 数据规模与约定

    对于所有测试数据,保证: 1 ≤ T ≤ 5 , 1 ≤ n , m ≤ 10 3 , 1 ≤ k ≤ 10 6 , 1 ≤ x 0 ≤ n , 1 ≤ y 0 ≤ m , 0 ≤ d 0 ≤ 3 1 ≤ T ≤ 5,\ 1 ≤ n,m ≤ 10^3,\ 1 ≤ k ≤ 10^6,\ 1 ≤ x_0 ≤ n,\ 1 ≤ y_0 ≤ m,\ 0 ≤ d_0 ≤ 3 1≤T≤5, 1≤n,m≤103, 1≤k≤106, 1≤x0≤n, 1≤y0≤m, 0≤d0≤3,且机器人的起始位置为空地。

思路分析

此题考查网格图模拟与方向向量,属于基础题。

由于 k ≤ 10 6 k \le 10^6 k≤106,我们可以按照题目规则直接进行 k k k 次模拟。设置数组 dx[4] = {0, 1, 0, -1} 和 dy[4] = {1, 0, -1, 0},依次对应东、南、西、北。使用布尔数组 vis[N][N] 记录每个位置是否被访问过。初始时标记起点 vis[x0][y0] = true,并将答案计数器 ans 置为 1 1 1。计算下一步拟到达的位置 ( n x , n y ) = ( x + d x d , y + d y d ) (nx, ny) = (x + dxd, y + dyd) (nx,ny)=(x+dxd,y+dyd)。

  • 若 1 ≤ n x ≤ n 1 \le nx \le n 1≤nx≤n 且 1 ≤ n y ≤ m 1 \le ny \le m 1≤ny≤m 且 grid[nx][ny] == '.',移动成功:更新 ( x , y ) = ( n x , n y ) (x, y) = (nx, ny) (x,y)=(nx,ny)。若该点此前未被访问,标记并使得 ans++;
  • 否则,移动失败:更新方向 d = ( d + 1 )   m o d   4 d = (d + 1) \bmod 4 d=(d+1)mod4。

每组测试数据的时间复杂度为 O ( k ) O(k) O(k),空间复杂度为 O ( n × m ) O(n \times m) O(n×m),完全可以通过。

cpp 复制代码
/*
 * Name: T2.cpp
 * Problem: 地图探险
 * Author: Teacher Gao.
 * Date&Time: 2026/09/29 19:50
 */

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

const int MAXN = 1005;
const int dx[] = {0, 1, 0, -1};
const int dy[] = {1, 0, -1, 0};

void solve() {
    char grid[MAXN][MAXN];
    bool vis[MAXN][MAXN] = {0};
    int n, m, k, x, y, d;
    cin >> n >> m >> k >> x >> y >> d;
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            cin >> grid[i][j];
        }
    }

    vis[x][y] = true;
    int ans = 1;
    while (k--) {
        int nx = x + dx[d];
        int ny = y + dy[d];

        if (nx >= 1 && nx <= n && ny >= 1 && ny <= m && grid[nx][ny] == '.') {
            x = nx;
            y = ny;
            if (!vis[x][y]) {
                vis[x][y] = true;
                ans++;
            }
        } 
        else {
            d = (d + 1) % 4;
        }
    }

    cout << ans << "\n";
}

int main() {
    ios::sync_with_stdio(false); cin.tie(0);

    int T;
    cin >> T;
    while (T--) {
        solve();
    }

    return 0;
}

T3. 小木棍

题目链接:SOJ P5144

小 S S S 喜欢收集小木棍。在收集了 n n n 根长度相等的小木棍之后,他闲来无事,便用它们拼起了数字。用小木棍拼每种数字的方法如下图所示。

现在小 S S S 希望拼出一个正整数,满足如下条件:

  • 拼出这个数恰好 使用 n n n 根小木棍;
  • 拼出的数没有前导 0 0 0;
  • 在满足以上两个条件的前提下,这个数尽可能小。

小 S S S 想知道这个数是多少,可 n n n 很大,把木棍整理清楚就把小 S S S 折腾坏了,所以你需要帮他解决这个问题。如果不存在正整数满足以上条件,你需要输出 ‐1 进行报告。

时间限制:1 s

内存限制:512 MB

  • 输入

    本题有多组测试数据 。

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

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

    一行包含一个整数 n n n,表示木棍数。

  • 输出

    对于每组数据:输出一行,如果存在满足题意的正整数,输出这个数;否则输出 ‐1。

  • 样例输入

    a 复制代码
    5
    1
    2
    3
    6
    18
  • 样例输出

    a 复制代码
    -1
    1
    7
    6
    208
  • 数据规模与约定

    对于所有测试数据,保证: 1 ≤ T ≤ 50 1 ≤ T ≤ 50 1≤T≤50, 1 ≤ n ≤ 10 5 1 ≤ n ≤ 10^5 1≤n≤105。

思路分析

此题考查贪心算法与余数分类讨论,属于中等偏易题目。

要使拼出的数字尽可能小,有两个核心贪心策略:

  1. 位数越少越好 :消耗木棒最多的数字是 8(需 7 根)。因此对于大的 n n n,应该尽可能多地放置 8 来减少总位数。
  2. 高位数字越小越好 :首位不能为 0,其余位在允许的情况下优先选择较小的数字(如 0 消耗 6 根)。

我们可以观察 n   m o d   7 n \bmod 7 nmod7 的余数情况:

  • 若 n = 1 n = 1 n=1:无法拼出任何正整数,输出 -1。
  • 若 n ≤ 7 n \le 7 n≤7:直接根据最小值特判:
    • n = 2 → n=2 \to n=2→ 2
    • n = 3 → n=3 \to n=3→ 7
    • n = 4 → n=4 \to n=4→ 4
    • n = 5 → n=5 \to n=5→ 2
    • n = 6 → n=6 \to n=6→ 6(首位不能为 0)
    • n = 7 → n=7 \to n=7→ 8
  • 若 n > 7 n > 7 n>7:计算额外需要处理的余数 n   m o d   7 n \bmod 7 nmod7,优先处理最高 1 ∼ 2 1\sim 2 1∼2 位,后续位置全部填充 8:
    • n   m o d   7 = 0 n \bmod 7 = 0 nmod7=0:全部填 8(如 888...)。
    • n   m o d   7 = 1 n \bmod 7 = 1 nmod7=1:借 1 个 7 根(共 1 + 7 = 8 1+7=8 1+7=8 根),首两位拼出最小的 10,后续全部填 8。
    • n   m o d   7 = 2 n \bmod 7 = 2 nmod7=2:首位填 1(耗 2 根),后续全部填 8。
    • n   m o d   7 = 3 n \bmod 7 = 3 nmod7=3:借 1 个 7 根(共 3 + 7 = 10 3+7=10 3+7=10 根),首两位拼出最小的 22(或 200 等特例),最优前缀为 200(需 12 根,借 2 个 7)或 22(仅需 10 根,拼出 22 + 8...,或 200 + 8...)。更标准的表达为:余 3 时前缀选 200(消耗 17 根,减去 2 个 8),或直接打表前缀:22 开头再接 ( n − 10 ) / 7 (n-10)/7 (n−10)/7 个 8。
    • n   m o d   7 = 4 n \bmod 7 = 4 nmod7=4:借 1 个 7 根(共 4 + 7 = 11 4+7=11 4+7=11 根),首两位拼出 20,后续全部填 8。
    • n   m o d   7 = 5 n \bmod 7 = 5 nmod7=5:首位填 2(耗 5 根),后续全部填 8。
    • n   m o d   7 = 6 n \bmod 7 = 6 nmod7=6:首位填 6(耗 6 根),后续全部填 8。

因此,可以根据 n   m o d   7 n \bmod 7 nmod7 直接构造出对应的前缀和后接 8 的数量。

cpp 复制代码
/*
 * Name: T3.cpp
 * Problem: 小木棍
 * Author: Teacher Gao.
 * Date&Time: 2026/09/29 20:03
 */

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

void solve() {
    int n;
    cin >> n;

    if (n < 2) {
        cout << "-1\n";
        return;
    }

    if (n == 2) { cout << "1\n"; return; }
    if (n == 3) { cout << "7\n"; return; }
    if (n == 4) { cout << "4\n"; return; }
    if (n == 5) { cout << "2\n"; return; }
    if (n == 6) { cout << "6\n"; return; }
    if (n == 7) { cout << "8\n"; return; }

    int rem = n % 7, e8 = n / 7;

    if (rem == 0) {
        for (int i = 0; i < e8; i++) cout << '8';
    } 
    else if (rem == 1) {
        cout << "10";
        for (int i = 0; i < e8 - 1; i++) cout << '8';
    } 
    else if (rem == 2) {
        cout << '1';
        for (int i = 0; i < e8; i++) cout << '8';
    } 
    else if (rem == 3) {
		if (e8 == 1) cout << "22";
		else {
			cout << "200";
			for (int i = 0; i < e8 - 2; i++) cout << '8';	
		}
    } 
    else if (rem == 4) {
        cout << "20";
        for (int i = 0; i < e8 - 1; i++) cout << '8';
    } 
    else if (rem == 5) {
        cout << '2';
        for (int i = 0; i < e8; i++) cout << '8';
    } 
    else if (rem == 6) {
        cout << '6';
        for (int i = 0; i < e8; i++) cout << '8';
    }
    cout << "\n";
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);

    int T;
    cin >> T;
    while (T--) {
        solve();
    }

    return 0;
}

T4. 接龙

题目链接:SOJ P5145

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

总共有 n n n 个人参与这个接龙游戏,第 i i i 个人会获得一个整数序列 S i S_i Si 作为他的词库。一次游戏分为若干轮,每一轮规则如下:

  • n n n 个人中的某个人 p p p 带着他的词库 S p S_p Sp 进行接龙。若这不是游戏的第一轮,那么这一轮进行接龙的人不能与上一轮相同,但可以与上上轮或更往前的轮相同。
  • 接龙的人选择一个长度在 2 , k 2, k 2,k 的 S p S_p Sp 的连续子序列 A A A 作为这一轮的接龙序列 ,其中 k k k 是给定的常数。若这是游戏的第一轮,那么 A A A 需要以元素 1 1 1 开头,否则 A A A 需要以上一轮的接龙序列的最后一个元素开头。

序列 A A A 是序列 S S S 的连续子序列当且仅当可以通过删除 S S S 的开头和结尾的若干元素(可以不删除)得到 A A A。

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

时间限制:2.0 s

内存限制:512 MB

  • 输入

    本题有多组测试数据 。

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

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

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

    接下来 n n n 行:第 i i i 行包含 ( l i + 1 ) (l_i + 1) (li+1) 个整数 l i , S i , 1 , S i , 2 , ⋅ ⋅ ⋅ , S i , l i l_i, S_{i,1}, S_{i,2}, · · · , S_{i,l_i} li,Si,1,Si,2,⋅⋅⋅,Si,li,其中第一个整数 l i l_i li 表示序列 S i S_i Si 的长度,接下来 l i l_i li 个整数描述序列 S i S_i Si。

    接下来 q q q 行:第 j j j 行包含两个整数 r j , c j r_j , c_j rj,cj,描述一个任务。

  • 输出

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

  • 样例输入

    a 复制代码
    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
  • 样例输出

    a 复制代码
    1
    0
    1
    0
    1
    0
    0
  • 数据规模与约定

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

    • 1 ≤ T ≤ 5 1 ≤ T ≤ 5 1≤T≤5;
    • 1 ≤ n ≤ 10 5 1 ≤ n ≤ 10^5 1≤n≤105, 2 ≤ k ≤ 2 × 10 5 2 ≤ k ≤ 2 × 10^5 2≤k≤2×105, 1 ≤ q ≤ 10 5 1 ≤ q ≤ 10^5 1≤q≤105;
    • 1 ≤ l i ≤ 2 × 10 5 1 ≤ l_i ≤ 2 × 10^5 1≤li≤2×105, 1 ≤ S i , j ≤ 2 × 10 5 1 ≤ S_{i,j} ≤ 2 × 10^5 1≤Si,j≤2×105;
    • 1 ≤ r j ≤ 10 2 1 ≤ r_j ≤ 10^2 1≤rj≤102, 1 ≤ c j ≤ 2 × 10 5 1 ≤ c_j ≤ 2 × 10^5 1≤cj≤2×105;
    • 设 ∑ l \sum l ∑l 为单组测试数据内 所有 l i l_i li 的和,则 ∑ l ≤ 2 × 10 5 \sum l ≤ 2 × 10^5 ∑l≤2×105。

思路分析

此题考查动态规划与滑动区间标记,属于提高组难度的综合题。

根据接龙规则:

  1. 连续两轮不能由同一个人接龙 :如果第 r − 1 r-1 r−1 轮的结尾数字 v v v 是由玩家 p p p 拼出的,那么第 r r r 轮中除了玩家 p p p 以外的其他人 都可以用 v v v 作为开头。
  2. 多玩家接龙无限制 :如果第 r − 1 r-1 r−1 轮的结尾数字 v v v 可以由至少两个不同玩家 拼出,那么第 r r r 轮中所有人 都可以用 v v v 作为开头。

定义数组 f[r][j] 表示在第 r r r 轮中,以数字 j j j 结尾时的玩家可行性状态:

  • f[r][j] = -1:表示第 r r r 轮无法以数字 j j j 结尾(不可达)。
  • f[r][j] = 0:表示第 r r r 轮可以以数字 j j j 结尾,且有多个不同玩家都能接出该数字(下一轮所有人皆可接龙)。
  • f[r][j] = i(正整数):表示第 r r r 轮可以以数字 j j j 结尾,但只有玩家 i i i 能接出该数字(下一轮除了玩家 i i i 以外的人皆可接龙)。

对于第 r r r 轮,我们依次遍历每一位玩家 i i i 和他的序列 a i ai ai:

  • 维护一个变量 L(表示当前位置向后还能延伸的合法接龙步数)。
  • 每往后走一步,L--。只要 L > 0,说明当前位置 a i j aij aij 处于某个合法的接龙区间内,此时该位置可以作为第 r r r 轮的结尾:
    • 若 f[r][a[i][j]] == -1,说明这是第 r r r 轮首次有人接出数字 a i j aij aij,记录 f[r][a[i][j]] = i。
    • 若 f[r][a[i][j]] != -1 且不等于 i i i,说明之前已有其他玩家接出了该数字,更新 f[r][a[i][j]] = 0(标记为多人可行)。
  • 当扫描到某个数字 a i j aij aij 时,检查它是否可以作为第 r r r 轮的起点 (即 f[r-1][a[i][j]] != -1 且不等于 i i i)。如果可以,则将步数重新重置为 L = k!

外层循环最多 100 轮,内层循环扫描所有玩家的序列总长度 ∑ l i \sum l_i ∑li。总时间复杂度为 O ( 100 × ∑ l i ) O(100 \times \sum l_i) O(100×∑li),单次询问响应 O ( 1 ) O(1) O(1)。空间复杂度: O ( 100 × max ⁡ ( c ) ) O(100 \times \max(c)) O(100×max(c))。

cpp 复制代码
/*
 * Name: T4.cpp
 * Problem: 接龙
 * Author: Teacher Gao.
 * Date&Time: 2026/09/29 20:31
 */

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

const int N = 100005;

vector<int> a[N];
int f[105][N << 1];

int main() {
    int t;
    if (scanf("%d", &t) == EOF) return 0;

    while (t--) {
        int n, k, q, s, tmp, x, y;
        scanf("%d%d%d", &n, &k, &q);

        for (int i = 1; i <= n; i++) {
            a[i].clear();
            scanf("%d", &s);
            for (int j = 1; j <= s; j++) {
                scanf("%d", &tmp);
                a[i].push_back(tmp);
            }
        }

        memset(f, -1, sizeof(f));
        f[0][1] = 0;

        for (int r = 1; r <= 100; r++) {        // 1. 枚举轮次
            for (int i = 1; i <= n; i++) {      // 2. 枚举接龙的人
                int L = 0;                      // 当前可延伸的剩余步数
                for (int j = 0; j < a[i].size(); j++) { // 3. 枚举第 i 个人的词库
                    L--;
                    // 如果还在合法的接龙步数范围内,尝试更新第 r 轮结尾状态
                    if (L > 0) {
                        if (f[r][a[i][j]] == -1) {          // 第一次出现该结尾
                            f[r][a[i][j]] = i;              // 记录接龙玩家编号
                        }
                        else if (f[r][a[i][j]] != i) {      // 已经被其他玩家接出过
                            f[r][a[i][j]] = 0;              // 标记为多人可接龙
                        }
                    }

                    // 检查当前单词能否作为第 r-1 轮转移过来的合法开头(上一轮可达且不是当前玩家 i 结尾)
                    if (f[r-1][a[i][j]] != -1 && f[r-1][a[i][j]] != i) {
                        L = k; // 重置最大延伸距离为 k
                    }
                }
            }
        }

        for (int i = 1; i <= q; i++) {
            scanf("%d%d", &x, &y);
            if (f[x][y] == -1) printf("0\n");
            else printf("1\n");
        }
    }

    return 0;
}
相关推荐
Lazionr1 小时前
set和map的使用——从基础接口到实际应用
c++
重生之小比特1 小时前
【C++进阶】哈希表实现
开发语言·c++·哈希
m0_380743871 小时前
VisualStudio中OpenCV的创建与配置使用小结
开发语言·c++
凉茶钱2 小时前
【C++】动态内存管理完整解析:从内存划分到 new delete 底层原理
c语言·开发语言·c++·内存管理·动态内存
cu1432 小时前
细谈GM8775C的具体功能和应用
c语言·c++·人工智能·嵌入式硬件
小七在进步2 小时前
类和对象(五)
java·开发语言·算法
无名猿2 小时前
std::optional 完全指南:别再用 -1 和 nullptr 表达「没有值」
c++·标准库·现代c++·语法基础
就叫年华吧丶2 小时前
长文档点目录定位总是不准?一个 content-visibility + 平滑滚动引发的连环坑(Vue3 实战)
前端·javascript·算法·vue
FPGA信号处理2 小时前
【信号检测与估计】第四节课:非高斯噪声下的 BLUE、极大似然与 EM 算法
人工智能·算法·机器学习