唐巧的博客

CSPJ 教学思考:模拟

字数统计: 5.5k阅读时长: 27 min
2025/03/29

模拟是最有效的练习编程熟练度的基础算法,也是有效的掌握各种编程技巧的练习方式。

本文将把各种模拟技巧与题目结合,用题目带着学生掌握这些模拟技巧。

二维数组包边

有些时候,我们在处理二维数组的时候,需要处理 x,y 坐标的边界。这样写起来会比较麻烦,但是,如果我们将数据从下标 1 开始保存,那么就人为在数据外面留了一圈缓冲带。这个时候,在处理 x,y 周围坐标的时候,就不会出现数据下标越界的情况了。

例题:P2670 NOIP 2015 普及组 扫雷游戏

该题如果正常写,需要判断每个格子周围 8 个格子的状态。如果我们把数据从 1 开始读入,在判断的时候就容易很多。以下是参考代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/**
* P2670 [NOIP 2015 普及组] 扫雷游戏
*
* Author: Tang Qiao
*/
#include <bits/stdc++.h>
using namespace std;

int n, m;
char tu[110][110];
int movex[] = {-1, -1, -1, 0, 0, 1, 1, 1};
int movey[] = {-1, 0, 1, -1, 1, -1, 0, 1};

int main() {
cin >> n >> m;
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
cin >> tu[i][j];
}
}

for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
if (tu[i][j] == '*') continue;
int cnt = 0;
for (int k = 0; k < 8; ++k) {
int x = i + movex[k];
int y = j + movey[k];
if (tu[x][y] == '*') cnt++;
}
tu[i][j] = cnt + '0';
}
}

for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
cout << tu[i][j];
}
cout << endl;
}
return 0;
}

例题:B4248 语言月赛 202503 数字棋盘

本题也可以用包边的技巧,保证数据在检查的时候不会越界。参考代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <bits/stdc++.h>
using namespace std;

int n, m;
int a[1001][1001];
int x, y;

bool check(int i, int j) {
// 检查上方格子
if (i > 1 && a[i-1][j] == y) return true;
// 检查下方格子
if (i < n && a[i+1][j] == y) return true;
// 检查左侧格子
if (j > 1 && a[i][j-1] == y) return true;
// 检查右侧格子
if (j < m && a[i][j+1] == y) return true;
return false;
}

int main() {
ios::sync_with_stdio(false);
cin >> n >> m;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
cin >> a[i][j];
}
}
cin >> x >> y;
int count = 0;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (a[i][j] == x && check(i, j)) {
count++;
}
}
}
cout << count << endl;
return 0;
}

P10719 GESP202406 五级 黑白格

此题需要求枚举从坐标(x,y)到坐标(a,b)的 1 的个数。我们先用将从(0,0)到(a,b)的 1 的个数保存在一个数组 s[110][110]中,然后再通过容斥原理来进行快速求(i,j)到(a,b)中 1 的个数。具体方法如下:

第一步:对于每一个 s[i][j],满足:s[i][j] = s[i-1][j] + cnt,其中 cnt 为第 i 行前 j 个数中 1 的个数。于是,我们就可以递推求出所有的 s[i][j],代码如下:

1
2
3
4
5
6
7
for (int i = 1; i <= n; i++) {
int cnt = 0;
for (int j = 1; j <= m; j++) {
cnt += (tu[i][j] == '1');
s[i][j] = s[i-1][j] + cnt;
}
}

以上代码使用了“包边”的技巧,因为我们下标是从 1 开始的,所以下标 i-1 不会越界。

第二步:根据容斥原理。从坐标(i,j)到坐标(a,b)的 1 的个数为:s[a][b] - s[i-1][b] - s[a][j-1] + s[i-1][j-1]。如下图所示:

以上公式如果使用“包边”技巧,让有效坐标从 1 开始,也会帮助 i-1 的值不会越界。

完整代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/**
* Author: Tang Qiao
*/
#include <bits/stdc++.h>
using namespace std;

int n, m, k, ans;
int s[110][110]; // 表示从(0,0)到(a,b)的 1 的个数
char tu[110][110];

int main() {
cin >> n >> m >> k;
for (int i = 1; i <= n; i++) {
cin >> tu[i]+1;
}
// 从第二行递推
for (int i = 1; i <= n; i++) {
int cnt = 0;
for (int j = 1; j <= m; j++) {
cnt += (tu[i][j] == '1');
s[i][j] = s[i-1][j] + cnt;
}
}
ans = INT_MAX;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
for (int a = i; a <= n; a++) {
for (int b = j; b <= m; b++) {
int cnt = s[a][b] - s[i-1][b] - s[a][j-1] + s[i-1][j-1];
if (cnt >= k) {
int tmp = (a-i+1) * (b-j+1);
if (tmp < ans) ans = tmp;
}
}
}
}
}
if (ans == INT_MAX) cout << 0 << endl;
else cout << ans << endl;
return 0;
}

围圈数数

有一种模拟题,要求我们把人围成一个圈,在圈上数数,然后问你数到的是谁。类似于小时候玩的“点兵点将”游戏,可能是顺时针数,也可能是逆时针数。

对于这种数数题目,最简单的做法是:直接用加减来进行目标的选择。加减之后,下标可能变负数或者超过总数,这个时候进行简单的取模调整,就可以将下标调整正常。

例题:P1563 玩具谜题

此题我们:

  • idx = (idx + b) % n; 来完成顺时针数
  • idx = (idx - b + n) % n; 来完成逆时针数

通过这样的简单的加减和取模,保证能够快速跳到目标位置,完成模拟操作。完整代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/**
* Author: Tang Qiao
*/
#include <bits/stdc++.h>
using namespace std;
#define MAXN int(1e5 + 10)

int n, m;
int face[MAXN];
string name[MAXN];

int main() {
cin >> n >> m;
for (int i = 0; i < n; ++i) {
cin >> face[i] >> name[i] ;
}

int idx = 0;
for (int i = 0; i < m; ++i) {
int a, b;
cin >> a >> b;
// 圈内向左 == 圈外向右
if ((face[idx] == 0 && a == 0)
|| (face[idx] == 1 && a == 1)) {
idx = (idx - b + n) % n;
} else {
idx = (idx + b) % n;
}
}
cout << name[idx] << endl;
return 0;
}

例题:B4246 环形游走

此题有个技巧:就是走的时候可能绕多圈,这个时候我们先把要走的步数模 n: step % n, 这样就把前面的多圈跳过了,也不会把坐标减成特别特别小的负数。

参考代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* Author: Tang Qiao
*/
#include <bits/stdc++.h>
using namespace std;

int main() {
int n, m;
cin >> n >> m;
vector<int> a(n);
for (int i = 0; i < n; i++) {
cin >> a[i];
}
int current = 0;
for (int i = 0; i < m; i++) {
int step = a[current] % n;
current = (current - step + n) % n;
}
cout << current + 1 << endl;
return 0;
}

更多练习:

例题:B3921 小杨的考试

绕圈一类的问题不止是以上那种真实的圈,也可能是像星期几这样逻辑上的圈(日期就像是一个圈,从星期一到星期日,然后又回到星期一)。

B3921 GESP202312 一级 小杨的考试这道题让我们计算日期。最简单的办法,是让星期几先落到 0-6 的表示法(0 表示星期一,6 表示星期日)。然后我们就可以用简单的加 N 天,然后模 7,快速定位到未来是星期几。对于过去,我们也可以简单通过减 N%7 天,然后减掉差的天数后 +7 再模 7,让结果落到 0-6 上。

参考代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* Author: Tang Qiao
*/
#include <bits/stdc++.h>
using namespace std;

int x, n;
int main() {
cin >> x >> n;
x = (x - 1 + n) % 7 + 1;
cout << x << endl;
return 0;
}

矩阵操作

矩阵操作这类模拟题,会要求我们在一个二维(或三维)的数组上进行各种操作,包括填充,旋转,查找,合并等。需要我们熟悉各种矩阵操作的技巧。

例题:P5725 求三角形

此题是一道基础的填充题。

  • 对于第一种要求,我们用在二维数组上填充实现。
  • 对于第二种要求,我们直接输出结果,在合适的位置加上一些空格。

示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/**
* Author: Tang Qiao
*/
#include <bits/stdc++.h>
using namespace std;

int tu[11][11];
int n;
int main() {
cin >> n;

// 处理第一种要求
int cnt = 1;
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= n; ++j) {
tu[i][j] = cnt++;
}
}
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= n; ++j) {
printf("%02d", tu[i][j]);
}
printf("\n");
}
printf("\n");
// 处理第二种要求
cnt = 1;
int bk = n-1;
for (int i = 1; i <= n; ++i, bk--) {
for (int j = 1; j <= bk; ++j) printf(" ");
for (int j = 1; j <= i; ++j) {
printf("%02d", cnt++);
}
printf("\n");
}

return 0;
}

例题:P5461 赦免战俘

此题我们需要熟练使用递归来进行标记。参考代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/**
* Author: Tang Qiao
*/
#include <bits/stdc++.h>
using namespace std;

int n, m;
char v[1100][1100];

void mark(int x, int y, int size) {
if (size == 1) return;
int half = size/2;
for (int i = x; i < x+half; ++i) {
for (int j = y; j < y+half; ++j) {
v[i][j] = '0';
}
}
mark(x, y+half, half);
mark(x+half, y, half);
mark(x+half, y+half, half);
}

int main() {
cin >> n;
m = 1<<n;
memset(v, '1', sizeof(v));
mark(0, 0, m);
for (int i = 0; i < m; ++i) {
for (int j = 0; j < m; ++j) {
cout << v[i][j] << " ";
}
cout << endl;
}
return 0;
}

例题:P5731 蛇形方阵

蛇形方阵是一道基础题,用于练习二维数组上的操作。我使用的模拟技巧是:

  • 定义一个 order 变量,表示当前方向
  • 与 order 变量配合,定义一个 movex 和 movey 数组,表示当前方向的移动

相关代码是:

1
2
3
int order;
int movex[] = {0, 1, 0, -1};
int movey[] = {1, 0, -1, 0};

每次移动,先判断是否越界或者已经填充过值:

  • 如果越界或已经填充过值,则改变方向再移动
  • 如果没越界,则移动

关键代码如下:

1
2
3
4
5
if (nx < 1 || nx > n || ny < 1 || ny > n || tu[nx][ny] != 0) {
order = (order + 1) % 4;
nx = x + movex[order];
ny = y + movey[order];
}

因为要填充 n*n 个数,所以循环一共执行 n*n 次。

完整的参考代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/**
* Author: Tang Qiao
*/
#include <bits/stdc++.h>
using namespace std;

int n, x, y, order;
int tu[15][15];
int movex[] = {0, 1, 0, -1};
int movey[] = {1, 0, -1, 0};
int main() {
cin >> n;
memset(tu, 0, sizeof(tu));
x = 1;
y = 0;
order = 0;
for (int i = 1; i <= n*n; i++) {
int nx = x + movex[order];
int ny = y + movey[order];
if (nx < 1 || nx > n || ny < 1 || ny > n || tu[nx][ny] != 0) {
order = (order + 1) % 4;
nx = x + movex[order];
ny = y + movey[order];
}
x = nx;
y = ny;
tu[x][y] = i;
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
printf("%3d", tu[i][j]);
}
printf("\n");
}
return 0;
}

例题:P4924 魔法少女小Scarlet

本题涉及矩阵的旋转,实际操作起来还是有点麻烦。这里我们按旋转的中心来重建坐标系的话,可以观察到如下规律:

  • 顺时针旋转:(a, b) -> (b, -a)
  • 逆时针旋转:(a, b) -> (-b, a)

这样,我们就可以构建关键的旋转代码了,假如我们是基于中心点 (x, y) 半径是 r 的顺时针旋转的话,那么,对于坐标 (a, b),我们:

  • 首先:把它移动到以 (x, y) 为中心:(a-x, b-y)
  • 然后:我们把坐标按上面的规则变换成 (b-y, x-a)
  • 最后:我们把坐标加上 (x, y) 的偏移,还原成原始坐标:(b-y+x, x-a+y)

以上逻辑写成代码是:g[b-y+x][x-a+y]=f[a][b]

同理,如果是逆时针旋转:

  • 首先:把它移动到以 (x, y) 为中心:(a-x, b-y)
  • 然后:我们把坐标按上面的规则变换成 (y-b, a-x)
  • 最后:我们把坐标加上 (x, y) 的偏移,还原成原始坐标:(y-b+x, a-x+y)

以上逻辑写成代码是:g[y-b+x][a-x+y]=f[a][b]

本题保证了数据不会在旋转时越界,整体的参考代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/**
* Author: Tang Qiao
*/
#include <bits/stdc++.h>
using namespace std;

#define MAXN 510
int f[MAXN][MAXN], g[MAXN][MAXN];
int n, m;
int main() {
cin >> n >> m;
int cnt = 1;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
f[i][j] = cnt++;
}
}
for (int i = 1; i <=m; ++i) {
int x, y, r, z;
cin >> x >> y >> r >> z;
if (z == 0) {
for (int a = x-r; a <= x+r; ++a)
for (int b = y-r; b <= y+r; ++b)
g[b-y+x][x-a+y]=f[a][b];

} else {
for (int a = x-r; a <= x+r; ++a)
for (int b = y-r; b <= y+r; ++b)
g[y-b+x][a-x+y]=f[a][b];
}

for (int a = x-r; a <= x+r; ++a)
for (int b = y-r; b <= y+r; ++b)
f[a][b] = g[a][b];
} // end of m
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
cout << f[i][j] << " ";
}
cout << endl;
}
return 0;
}

例题:P1205 USACO1.2 方块转换 Transformations

此题需要推导矩阵旋转的规律。我们可以把原坐标和新坐标写下来,做成一个表格。

然后,我们把坐标的变化写成下面的表格形式:

通过观察,我们发现:

  • 黄色和红色的坐标在变换前后刚好相等,即: 新 x = 原 y
  • 两侧的白色的坐标加和刚好等于 n-1,即:原 x + 新 y = n - 1 => 新 y = n - 原 x - 1

综上,坐标变换公式为:新(y, n-x-1)=原(x, y)

所以,坐标变换相关代码为:

1
2
3
4
5
for (int x = 0; x < n; ++x) {
for (int y = 0; y < n; ++y) {
tmp[y][n-x-1] = ori[x][y];
}
}

与此类似,我们可以推出“反射”的代码关系是 新(x,n-y-1)=原(x,y),相关变换代码为:

1
2
3
4
5
for (int x = 0; x < n; ++x) {
for (int y = 0; y < n; ++y) {
tmp[x][n-y-1] = ori[x][y];
}
}

完整的参考代码如下(可以把 debug 变量设置成 true 来查看执行过程):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
/**
* Author: Tang Qiao
*/
#include <bits/stdc++.h>
using namespace std;

char ori[12][12], dest[12][12];
char tmp[12][12], tmp2[12][12];
int n;
bool debug = false;

bool match(char a[12][12], char b[12][12]) {
for (int x = 0; x < n; ++x) {
for (int y = 0; y < n; ++y) {
if (a[x][y] != b[x][y]) return false;
}
}
return true;
}

void print(char a[12][12]) {
for (int i = 0; i < n; ++i) {
cout << a[i] << endl;
}
}

int main() {
cin >> n;
for (int i = 0; i < n; ++i) {
cin >> ori[i];
}
for (int i = 0; i < n; ++i) {
cin >> dest[i];
}
// 方案一
for (int x = 0; x < n; ++x) {
for (int y = 0; y < n; ++y) {
tmp[y][n-x-1] = ori[x][y];
}
}
if (debug) {
cout << "debug 1: " << endl;
print(tmp);
}
if (match(tmp, dest)) {
cout << "1" << endl;
return 0;
}
// 方案二
for (int x = 0; x < n; ++x) {
for (int y = 0; y < n; ++y) {
tmp2[y][n-x-1] = tmp[x][y];
}
}
if (match(tmp2, dest)) {
cout << "2" << endl;
return 0;
}
// 方案三
for (int x = 0; x < n; ++x) {
for (int y = 0; y < n; ++y) {
tmp[y][n-x-1] = tmp2[x][y];
}
}
if (match(tmp, dest)) {
cout << "3" << endl;
return 0;
}
// 反射
for (int x = 0; x < n; ++x) {
for (int y = 0; y < n; ++y) {
tmp[x][n-y-1] = ori[x][y];
}
}
if (match(tmp, dest)) {
cout << "4" << endl;
return 0;
}
// 反射+旋转90
for (int x = 0; x < n; ++x) {
for (int y = 0; y < n; ++y) {
tmp2[y][n-x-1] = tmp[x][y];
}
}
if (debug) {
cout << "debug 5-1: " << endl;
print(tmp2);
}
if (match(tmp2, dest)) {
cout << "5" << endl;
return 0;
}
// 反射+旋转180
for (int x = 0; x < n; ++x) {
for (int y = 0; y < n; ++y) {
tmp[y][n-x-1] = tmp2[x][y];
}
}
if (debug) {
cout << "debug 5-2: " << endl;
print(tmp);
}
if (match(tmp, dest)) {
cout << "5" << endl;
return 0;
}
// 反射+旋转270
for (int x = 0; x < n; ++x) {
for (int y = 0; y < n; ++y) {
tmp2[y][n-x-1] = tmp[x][y];
}
}
if (debug) {
cout << "debug 5-3: " << endl;
print(tmp2);
}
if (match(tmp2, dest)) {
cout << "5" << endl;
return 0;
}
// 不改变
if (match(ori, dest)) {
cout << "6" << endl;
return 0;
}
cout << "7" << endl;
return 0;
}

更多练习:

游戏模拟

游戏模拟类的题目通常会告诉你一个相对复杂一点的游戏规则,然后让你用程序将这个游戏规律实现,最终将游戏的结果输出出来。

这种题目一方面考查了读题能力,需要对游戏规则的理解清楚,另一方面则是要对游戏规则进行建模,用合适的数据结构实现游戏中的模拟。

以下是一些相关的题目。

题号 描述
P1042 NOIP 2003 普及组 乒乓球
P1328 NOIP 2014 提高组 生活大爆炸版石头剪刀布
P1518 USACO2.4 两只塔姆沃斯牛 The Tamworth Two
P1089 NOIP 2004 提高组 津津的储蓄计划
P1161 数组标记

滑动窗口

例题:P1614 爱与愁的心痛

此题的解法是:构造一个“滑动的窗口”。先求出前 m 个数的和,这相当于窗口的原始位置。之后每次让窗口往右移动一格。每次移动的时候,会将最左侧的数字剔除,同时纳入一个新数字。如下图所示:

我们在滑动窗口的时候,需要用这个变量,分别指向:

  • 当前窗口最左的数字 p1
  • 当前窗口下一个要加入的数字 p2
  • 在滑动的时候,不断更新当前窗口的值 tot

以下是关键代码:

1
2
3
4
5
6
7
8
p1 = 0;
p2 = m;
while (p2 < n) {
tot -= v[p1];
tot += v[p2];
ans = min(ans, tot);
p1++; p2++;
}

完整代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/**
* Author: Tang Qiao
*/
#include <bits/stdc++.h>
using namespace std;

int n, m, tot, ans;
int p1, p2;
int v[3300];
int main() {
cin >> n >> m;
for (int i = 0; i < n; ++i) {
cin >> v[i];
}
// 初使化滑动窗口
tot = 0;
for (int i = 0; i < m; ++i) {
tot += v[i];
}
ans = tot;
p1 = 0;
p2 = m;
// 滑动窗口,更新值
while (p2 < n) {
tot -= v[p1];
tot += v[p2];
ans = min(ans, tot);
p1++;
p2++;
}
cout << ans << endl;
return 0;
}

模拟输入输出

有一些模拟需要我们有比较复杂的输入和输出操作技巧。在模拟输入和输出的时候,常用的两个函数是 sscanfsnprintf,其中:

  • sscanf 允许我们从一个字符串中读入内容。
  • snprintf 允许我们将输出内容先输出到一个字符串中。

以下我们用例题来演示其用法。

例题:P1957 口算练习题

此题的输入长度不固定,我们需要先判断输入的长度。同时,输出的时候,我们还需要输出“输出内容”的长度。这对我们处理输入和输出都带来了挑战。

我们可以把表达式整行整行读入,再用 sscanfsnprintf 来进行分析处理。以下是相关的示意:

1
2
3
4
int a, b;
char s[100], out[100];
sscanf(s, "%d%d", &a, &b);
snprintf(out, sizeof(out), "%d+%d=%d", a, b, a + b);

另外,我们还需要一次读入一整行,我用的方法是用 scanf, 代码如下:

1
2
scanf("%[^\n]", s);
getchar();

需要注意,以上代码每读入一行,需要用 getchar() 将行末的换行给读掉。

我们也可以用 cin.getline(s, sizeof(s)); 来读取数据。

以下是完整的示意代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/**
* Author: Tang Qiao
*/
#include <bits/stdc++.h>
using namespace std;

int T, a, b;
char ch, s[100], out[100];

int main() {
scanf("%d", &T); getchar();
while (T--) {
scanf("%[^\n]", s); getchar();
if (s[0] >='0' && s[0] <= '9') { // 也可使用函数: isdigit(s[0])
sscanf(s, "%d%d", &a, &b);
} else {
sscanf(s, "%c%d%d", &ch, &a, &b);
}
memset(out, 0, sizeof(out));
if (ch == 'a') {
snprintf(out, sizeof(out), "%d+%d=%d", a, b, a + b);
} else if (ch == 'b') {
snprintf(out, sizeof(out), "%d-%d=%d", a, b, a - b);
} else if (ch == 'c') {
snprintf(out, sizeof(out), "%d*%d=%d", a, b, a * b);
}
printf("%s\n", out);
printf("%lu\n", strlen(out));
}
return 0;
}

以上的 scanf 部分如果替换成 cin,示意代码如下:

1
2
3
4
5
6
7
8
9
int main() {
cin >> T;
cin.getline(s, sizeof(s));
while (T--) {
cin.getline(s, sizeof(s));
// 省略
}
return 0;
}

例题:P1067 多项式输出

此题是 NOIP 2009 普及组的题目。此题练习了 snprintf 的使用。同时,此题用 printf 的 %+d 可以保证正数输出带+号。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
/**
* Author: Tang Qiao
*/
#include <bits/stdc++.h>
using namespace std;

int n;
string ans;
char outs[100];

int main() {
ans = "";
cin >> n;
for (int i = n; i>=0; i--) {
int a;
cin >> a;
// 系数为0,跳过
if (a == 0) continue;
// 指数为0,单独处理
if (i == 0) {
memset(outs, 0, sizeof(outs));
snprintf(outs, sizeof(outs), "%+d", a);
ans += outs;
} else {
// 先处理系数
if (a == 1) {
snprintf(outs, sizeof(outs), "+x");
} else if (a == -1) {
snprintf(outs, sizeof(outs), "-x");
} else {
snprintf(outs, sizeof(outs), "%+dx", a);
}
ans += outs;
// 再处理指数
memset(outs, 0, sizeof(outs));
if (i == 1) {
snprintf(outs, sizeof(outs), "");
} else {
snprintf(outs, sizeof(outs), "^%d", i);
}
ans += outs;
}
}
if (ans[0] == '+') {
ans = ans.substr(1);
}
cout << ans << endl;
return 0;
}

更多练习:

其它模拟题目

待补充。

题号 描述
CATALOG
  1. 1. 二维数组包边
    1. 1.1. 例题:P2670 NOIP 2015 普及组 扫雷游戏
    2. 1.2. 例题:B4248 语言月赛 202503 数字棋盘
    3. 1.3. P10719 GESP202406 五级 黑白格
  2. 2. 围圈数数
    1. 2.1. 例题:P1563 玩具谜题
    2. 2.2. 例题:B4246 环形游走
    3. 2.3. 例题:B3921 小杨的考试
  3. 3. 矩阵操作
    1. 3.1. 例题:P5725 求三角形
    2. 3.2. 例题:P5461 赦免战俘
    3. 3.3. 例题:P5731 蛇形方阵
    4. 3.4. 例题:P4924 魔法少女小Scarlet
    5. 3.5. 例题:P1205 USACO1.2 方块转换 Transformations
  4. 4. 游戏模拟
  5. 5. 滑动窗口
    1. 5.1. 例题:P1614 爱与愁的心痛
  6. 6. 模拟输入输出
    1. 6.1. 例题:P1957 口算练习题
    2. 6.2. 例题:P1067 多项式输出
  7. 7. 其它模拟题目