CSP-J 2024题解

[CSP-J 2024 T1] 扑克牌

考点:模拟,桶

题意:给定 \(n\) 张牌的花色和点数,问52种牌中有几种是没出现过的。

解法:我们可以用 \(st[i][j]\) 表示花色为 \(i\),点数为 \(j\) 的牌是否出现过,\(st[i][j] = true\) 表示出现过,否则表示没出现过。那么代码就很显而易见了。

#include <bits/stdc++.h>
using namespace std;
const int N = 200 + 10;

bool st[N][N];

int main() {
    int n; cin >> n;
    int cnt = 0; // 表示一共出现过几种牌
    for (int i = 1; i <= n; i ++ ) {
        char a, b;
        cin >> a >> b;
        // st[a][b] == false 表示花色为a,点数为b的牌未出现过,这次是第一次出现
        if (!st[a][b]) cnt ++;
        st[a][b] = true;
    }
    cout << 52 - cnt << endl;
    return 0;
}

[CSP-J 2024 T2] 地图探险

考点:阅读理解,模拟

题意:题目非常的长,但是重要的部分就只有以下:

  • 有一个 \(n \times m\) 的地图,每个位置可能是 . 或者 x ,分别表示可以行走与无法通行
  • 小A每次可以从当前位置移动到他所朝向的相邻位置上,方向用0、1、2、3表示,分别表示东南西北。
  • 小A一开始处于 \((x, y)\) 且方向朝向为 \(d\)
  • 小A一共可以做k次操作,每次操作他都会做以下动作:
    • 计算出下一个位置在哪,记做 \((x',y')\)
    • 如果 \((x',y')\) 在界内,并且是可以行走的,就移动到该位置;否则就将朝向改作下一个方向,即 \(d = (d + 1) \% 4\)
  • 做完k次操作以后,小A一共走过了几个不同的位置。

按照题意模拟即可,个人认为本题难度全在读题上。

时间复杂度: \(O(n)\)

#include <bits/stdc++.h>
using namespace std;
const int N = 1e3 + 10;

char g[N][N];		// 表示地图
bool st[N][N];	// st[i][j] = true表示已经走过(i,j),否则表示没有
int dx[] = {0, 1, 0, -1};
int dy[] = {1, 0, -1, 0};

void solve() {
    int n, m, k;
    cin >> n >> m >> k;
    int x, y, d;
    cin >> x >> y >> d;
    for (int i = 1; i <= n; i ++ ) {
        for (int j = 1; j <= m; j ++ ) cin >> g[i][j];
    }
    // 由于有多组输入,因此需要把st数组清空
    for (int i = 1; i <= n; i ++ ) {
        for (int j = 1; j <= m; j ++ ) st[i][j] = false;
    }
    st[x][y] = true; // 不要忘记把起点标记为走过
    while (k -- ) {
        int nx = x + dx[d], ny = y + dy[d];		// 计算出下一个位置
        if (nx < 1 || nx > n || ny < 1 || ny > m || g[nx][ny] == 'x') {
            // 如果下一个位置非法,那么就需要向右转
            d = (d + 1) % 4;
            continue;
        }
        // 否则就标记这个点,然后移动
        st[nx][ny] = true;
        x = nx, y = ny;
    }
    int cnt = 0;
    for (int i = 1; i <= n; i ++ ) {
        for (int j = 1; j <= m; j ++ ) {
            if (st[i][j]) cnt ++;
        }
    }
    cout << cnt << endl;
}

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

[CSP-J 2024 T3] 小木棍

考点:贪心

题意:给定n条木棍,问能够拼出的最小正整数是多少,不带前导0。

解法:木棍消耗情况如下:

  • 1根:无
  • 2根:1
  • 3根:7
  • 4根:4
  • 5根:2、3、5
  • 6根:0、6、9
  • 7根:8

由于需要拼出的正整数最小,而位数更少的一定更小,相同位数的比较字典序即可。因此我们优先保证位数尽可能少,在此前提下从高到低位贪心地找每一位数字,使得高位的数字尽可能小即可。这里的5根和6根的消耗中有多个数字,我们取最小的那个就行了。

代码实现中有细节需要注意,例如0不可以做第一位,如果遇到0做第一位就需要用6来代替。

时间复杂度:\(O(n)\)

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1e3 + 10;

// f[i]表示i这个数字需要消耗多少木棍
int f[] = {6, 2, 5, 5, 4, 5, 6, 3, 7, 6};

void solve() {
    int n; cin >> n;
  	if (n == 1) {
      	// 无解
      	cout << -1 << endl;
      	return ;
    }
    int num = (n + 6) / 7;	// 计算位数
    int cnt = 0;	// 表示当前拼了cnt位
    while (n > 0) {
        int now = 99999;	// 表示用了t根,当前位的数字为now
        for (int i = 0; i < 10; i ++ ) {
            if (n < f[i]) continue; // 当前根数不够拼当前数位
            // 之前拼的位数+1+剩余可以拼的位数<=num才能满足题目要求
            if (cnt + 1 + (n - f[i] + 6) / 7 > num) continue;
            // 特判前导0   
            if (cnt == 0 && i == 0) continue;
            now = min(now, i);
        }
        n -= f[now];
        cout << now;
        cnt ++;
    }
    cout << endl;
}

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

[CSP-J 2024 T4] 接龙

考点:动态规划

解法:我们注意到,轮数最多就100轮,而且接龙的过程非常像“状态”的转移,所以我们考虑使用DP来解决。

从轮数下手,当前轮数的某个状态可以用上一轮转移过来,不妨直接定义 \(dp_{i,j}\) 表示进行到第 \(i\) 轮,以数字 \(j\) 结尾的人是谁,具体如下

\[dp_{i,j}= \begin{cases} 0& \text{有多人能满足}\\ x& \text{只有x能够满足}\\ -1& \text{没有人能满足} \end{cases} \]

我们考虑转移,例如我们当前到第 \(t\) 轮,遍历第 \(i\) 人的序列,遍历到 \(S_{i,j}\) 时,考虑能不能从 \(S_{i,j - k + 1} \text{到} S_{i,j - 1}\) 中的某个数为起点接龙过来。换句话说,只要存在 \(w \in [j-k+1,j-1]\) 使得 \(dp_{t-1,S_{i,w}}\) 合法(不是 -1 且不是 i)即可。直接双指针遍历就可以。

时间复杂度 \(O(rl)\)

#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 10;
const int INF = 0x3f3f3f3f;

int dp[110][N];    // dp[i][j] 表示
vector<int> a[N];  // a[i]表示第i人拥有的序列

void update(int x, int y, int val) {
    // 更新dp[x][y],其中当前是转移到第val人
    // 这里要注意,如果更新前dp[x][y]=val,那么是不变,而不是更新成0
    if (dp[x][y] == -1) dp[x][y] = val;
    else if (dp[x][y] != val) dp[x][y] = 0;
}

bool check(int x, int y, int pos) {
    // 当前人是第pos,检查dp[x][y]是否合法
    if (dp[x][y] == -1) return false;
    if (dp[x][y] == pos) return false;
    return true;
}

void solve() {
    memset(dp, -1, sizeof dp);  // 多组测试数据,别忘了初始化dp数组。
    int n, k, q;
    cin >> n >> k >> q;
    for (int i = 1; i <= n; i ++ ) {
        a[i].clear();
        int l; cin >> l;
        for (int j = 1; j <= l; j ++ ) {
            int x; cin >> x;
            a[i].push_back(x);
        }
    }
    dp[0][1] = 0;  // 由于第一轮需要从1开始,所以要保证从1开始是可以被更新的,故更新成0
    for (int i = 1; i <= 100; i ++ ) {
        for (int j = 1; j <= n; j ++ ) {
            int m = (int)a[j].size();
            int last = 0; // 区间起点指针
            int cnt = 0;  // 当前区间内可以作为起点的点个数
            if (check(i - 1, a[j][0], j)) cnt = 1;
            for (int h = 1; h < m; h ++ ) {
                if (h - last + 1 > k) {
                    // 如果当前区间超了,把左端点提前
                    if (check(i - 1, a[j][last], j)) cnt --;
                    last ++;
                }
            	// 如果区间内有可以作为起点的点,就更新
                if (cnt > 0) update(i, a[j][h], j);
            	// 更新当前点
                if (check(i - 1, a[j][h], j)) cnt ++;
            }
        }
    }
    // 预处理完成,直接输出答案
    while (q -- ) {
        int r, c; cin >> r >> c;
        if (dp[r][c] == -1) cout << 0 << '\n';
        else cout << 1 << '\n';
    }
}

int main() {
    ios::sync_with_stdio(false), cin.tie(0);
    int T; cin >> T;
    while (T -- ) solve();
    return 0;	
}
posted @ 2024-11-06 09:43  Time_Limit_Exceeded  阅读(95)  评论(0编辑  收藏  举报