CSP-J 2024 第二轮完整解析

CSP-J 2024 T1 扑克牌

题目分析

题目给出已有的牌,要求计算最少需要补充多少张牌才能凑齐一副完整的扑克牌(52张)。给出的扑克牌可能包含重复的牌(即花色和点数都相同的牌),因此需要去重后计算缺失的牌数。

代码

#include <iostream>
#include <set>
using namespace std;


int main() {
    int n;
    cin >> n;
    set<string> st;
    while (n--) {
        string s;
        cin >> s;
        st.insert(s);
    }
    cout << 52 - st.size() << endl;
    return 0;
}

CSP-J 2024 T2 地图探险

题目大意

有一片丛林,其环境可以用一个二维数组表示:

  • .代表此地为空地,可走。
  • x代表此地非空地,不可走。

有一个机器人探路,他的状态分为两个值:

  • 位置。
  • 方向。

机器人探路有这样一些规则:

  • 若前方有路,直接往前走(不管是否走过)。
  • 遇到障碍物,右转。

现在给出机器人的初始状态(一定在空地),求执行k次操作之后,机器人经过了多少地方(右转算一次操作,前进算一次操作)。

思路

需要的变量 / 数组

描述机器人状态需要的3个变量:

  • x:位置。
  • y:位置。
  • d:朝向(0~3)。

vis数组:

  • 当机器人走过[x, y],vis[x][y] = true

dir数组:

  • dir[4][2] = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}}
  • [x + dir[d][0], y + dir[d][1]]表示当机器人朝向为d时往前走一步之后的位置。
  • d = 0: 右 (0, 1)。
  • d = 1: 下 (1, 0)。
  • d = 2: 左 (0, -1)。
  • d = 3: 上 (-1, 0)。

核心思想

vis[x][y] = true;
while (k--) {
    int nx = x + dir[d][0], ny = y + dir[d][1]; // 计算下一步的位置
    if (!(nx >= 1 && nx <= n && ny >= 1 && ny <= m && mp[nx][ny] == '.')) { // 是否能走
        d = (d + 1) % 4; // 若不能走 右转
        continue;
    }
    x = nx, y = ny; // 若能走 更新
    vis[nx][ny] = true;
}

代码

#include <iostream>
#include <cstring>
using namespace std;

const int N = 1e3 + 5;

int n, m, k, x, y, d, dir[4][2] = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};
char mp[N][N];
bool vis[N][N];


int main() {
    int t;
    cin >> t;
    while (t--) {
        memset(vis, false, sizeof (vis));
        cin >> n >> m >> k >> x >> y >> d;
        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= m; j++)
                cin >> mp[i][j];

        vis[x][y] = true;
        while (k--) {
            int nx = x + dir[d][0], ny = y + dir[d][1];
            if (!(nx >= 1 && nx <= n && ny >= 1 && ny <= m && mp[nx][ny] == '.')) {
                d = (d + 1) % 4;
                continue;
            }
            x = nx, y = ny;
            vis[nx][ny] = true;
        }
        int ans = 0;
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= m; j++) {
                if (vis[i][j] == true)
                    ans++;
            }
        }
        cout << ans << endl;
    }
    return 0;
}

CSP-J 2024 T2 小木棍

题目大意

小 S 有 n 根小木棍,希望拼出一个正整数,满足如下条件:

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

思路

拼出0~9分别需要的木棍数量:

数字所需木棍数
06
12
25
35
44
55
66
73
87
96

求解思想:

  • 因为拼出8所需的木棍最多(7根),所以用8,可以使位数更少
  • 在保证位数最少的前提下,还要尽可能把高位的数字变小(必须保证位数最小)

按照思想,可以找到这样的规律:

条件构造方案
n == 1无解
n == 21
n == 37
n == 44
n == 52
n == 66
n == 78
n == 1022
(特判除外)n % 7 == 0(n / 7)个8
(特判除外)n % 7 == 110 + ((n - 8) / 7)个8
(特判除外)n % 7 == 21 + ((n - 2) / 7)个8
(特判除外)n % 7 == 3200 + ((n - 17) / 7)个8
(特判除外)n % 7 == 420 + ((n - 11) / 7)个8
(特判除外)n % 7 == 52 + ((n - 5) / 7)个8
(特判除外)n % 7 == 66 + ((n - 6) / 7)个8

代码

#include <iostream>
using namespace std;


string Solve(int n) {
    if (n == 1)
        return "-1";

    if (n % 7 == 0)
        return string(n / 7, '8');
    if (n % 7 == 1)
        return "10" + string((n - 8) / 7, '8');
    if (n % 7 == 2)
        return "1" + string((n - 2) / 7, '8');
    if (n % 7 == 3)
        return n == 3 ? "7" : n == 10 ? "22" : "200" + string((n - 17) / 7, '8');
    if (n % 7 == 4)
        return n == 4 ? "4" : "20" + string((n - 11) / 7, '8');
    if (n % 7 == 5)
        return "2" + string((n - 5) / 7, '8');
    if (n % 7 == 6)
        return "6" + string((n - 6) / 7, '8');
}


int main() {
    int t;
    cin >> t;
    while (t--) {
        int n;
        cin >> n;
        cout << Solve(n) << endl;
    }
    return 0;
}

CSP-J 2024 T4 接龙

题目大意

小 J 要进行一场 n 个人玩的接龙游戏,游戏的规则和定义如下:

  • 每个人都有自己的词库。
  • 游戏分为若干轮接龙,一个人连着接龙若干个词语算一轮,相邻两轮接龙不能由同一人完成。
  • 第一轮必须从 1 开始接龙,其他情况必须从上一轮的最后一个数开始接龙。
  • 每个人接龙都只能从自己的词库选择长度为 1 ~ k 的连续子序列接龙。
  • 示例:
    • 词库:[1, 2, 3, 5, 4]
    • 上一轮结尾:2
    • k = 2时可选子序列:
      [2] (长度1)
      [2,3] (长度2)

现在求能否接龙 r 轮,最后以 c 结尾。

输入样例解释

1 // 数据组数
3 3 7 // 人数 接龙序列长度上限(k) 询问次数
5 1 2 3 4 1 // 第 1 个人的词库
3 1 2 5 // 第 2 个人的词库
3 5 1 6 // 第 3 个人的词库
1 2 // 询问接龙 1 轮、以 2 结尾能否实现
......

解题思路

本文的解题思想为dp。
dp[r][x]接龙 r 轮、以 x 结尾的状态:

  • dp[r][x] = -1:不可实现。
  • dp[r][x] = i(一个自然数):可以通过词库 i 实现(最后一次接龙操作使用的是词库 i 中的某个连续子序列)。
  • dp[r][x] = -2:可以通过多个词库实现。

代码解析

完整的求解部分:

vector<vector<int> > Solve(int mx) {
    vector<vector<int> > dp(R, vector<int>(mx + 5, -1));
    dp[0][1] = -2;
    for (int r = 1; r <= 100; r++) {
        for (int i = 1; i <= n; i++) {
            int lst = -1;
            for (int j = 0; j < s[i].size(); j++) {
                int tmp = s[i][j];
                if (lst != -1 && j - lst + 1 <= k) {
                    if (dp[r][tmp] == -1)
                        dp[r][tmp] = i;
                    else if (dp[r][tmp] != i)
                        dp[r][tmp] = -2;
                }

                if (dp[r - 1][tmp] != -1 && dp[r - 1][tmp] != i)
                    lst = j;
            }
        }
    }
    return dp;
}

逐行分析

  • mx:所有词库中的最大值,用于构建dp的第二个维度。
  • dp的大小为R(最大轮数) * mx(所有词库中的最大值)
vector<vector<int> > Solve(int mx) {
	vector<vector<int> > dp(R, vector<int>(mx + 5, -1));
  • dp[0][1] = -2:作为虚拟起点,使得第一次接龙可以从数字 1 开始。
dp[0][1] = -2;
  • r:轮数。
  • i:尝试让第 i 个人接龙。
  • j:第 i 个人词库中第 j 个词语。
  • lst:当前序列 i 中最近一个满足接龙条件的起始位置(可以以lst为起点开始接龙)
for (int r = 1; r <= 100; r++) {
        for (int i = 1; i <= n; i++) {
            int lst = -1;
            for (int j = 0; j < s[i].size(); j++) {
            }
        }
    }
  • 获取当前人员 i 的词库中第 j 个词语。
  • 如果当前词库中有一个合适的起点可以开始接龙,并且长度合适:
    • 那么选择子序列[lst, j]开始接龙。
    • 更新当前状态。
  • 如果上一轮接龙不是 i 在接龙,并且可以以tmp结束,那么这一轮也可以从tmp开始接龙。
int tmp = s[i][j];
if (lst != -1 && j - lst + 1 <= k) {
    if (dp[r][tmp] == -1)
        dp[r][tmp] = i; // 首次发现路径则记录序列i
    else if (dp[r][tmp] != i)
        dp[r][tmp] = -2; // 若已有其他序列可达则标记为多路径(-2)
}

if (dp[r - 1][tmp] != -1 && dp[r - 1][tmp] != i)
    lst = j;

完整代码

#include <iostream>
#include <vector>
using namespace std;

const int N = 1e5 + 5, R = 105;
vector<int> s[N];
int n, k, q;

vector<vector<int> > Solve(int mx) {
    vector<vector<int> > dp(R, vector<int>(mx + 5, -1));
    dp[0][1] = -2;
    for (int r = 1; r <= 100; r++) {
        for (int i = 1; i <= n; i++) {
            int lst = -1;
            for (int j = 0; j < s[i].size(); j++) {
                int tmp = s[i][j];
                if (lst != -1 && j - lst + 1 <= k) {
                    if (dp[r][tmp] == -1)
                        dp[r][tmp] = i;
                    else if (dp[r][tmp] != i)
                        dp[r][tmp] = -2;
                }

                if (dp[r - 1][tmp] != -1 && dp[r - 1][tmp] != i)
                    lst = j;
            }
        }
    }
    return dp;
}


int main() {
    int t;
    cin >> t;
    while (t--) {
        int mx = -1;
        cin >> n >> k >> q;
        for (int i = 1; i <= n; i++) {
            int l;
            cin >> l;
            s[i].clear();
            s[i].resize(l);
            for (int j = 0; j < l; j++) {
                cin >> s[i][j];
                mx = max(mx, s[i][j]);
            }
        }
        
        vector<vector<int> > dp = Solve(mx);
        while (q--) {
            int r, c;
            cin >> r >> c;
            cout << (dp[r][c] != -1 ? 1 : 0) << endl;
        }
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值