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\) 结尾的人是谁,具体如下
我们考虑转移,例如我们当前到第 \(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;
}