- 试题下载
- 试题 A: 跑步训练
- 试题 B: 纪念日
- 试题 C: 合并检测
- 试题 D: REPEAT 程序
- 试题 E: 矩阵
- 试题 F: 整除序列
- 试题 G: 解码
- 试题 H: 走方格
- 试题 I: 整数拼接
- 试题 J: 网络分析
- 总结
试题下载
试题 A: 跑步训练
本题总分:5 分
【问题描述】
小明要做一个跑步训练。
初始时,小明充满体力,体力值计为 \(10000\) 。如果小明跑步,每分钟损耗 \(600\) 的体力。如果小明休息,每分钟增加 \(300\) 的体力。体力的损耗和增加都是均匀变化的。
小明打算跑一分钟、休息一分钟、再跑一分钟、再休息一分钟……如此循环。如果某个时刻小明的体力到达 \(0\) ,他就停止锻炼。
请问小明在多久后停止锻炼。为了使答案为整数,请以秒为单位输出答案。答案中只填写数,不填写单位。
【我的题解】
由于要以秒为单位输出答案,可以将单位统一成秒进行计算。虽然有点麻烦,但是很稳。
#include
using namespace std;
typedef long long LL;
typedef pair PII;
int slove(int n) {
int m = 0;
while (true) {
for (int i = 1; i <= 60; i++) {
n -= 10;
m += 1;
if (n == 0) return m;
}
for (int i = 1; i <= 60; i++) {
n += 5;
m += 1;
}
}
}
int main() {
printf("%d\n", slove(10000));
return 0;
}
我的答案 \(3880\)
试题 B: 纪念日
本题总分:5 分
【问题描述】
\(2020\) 年 \(7\) 月 \(1\) 日是中国 共 产 党 成立 \(99\) 周年纪念日。
中国 共 产 党 成立于 \(1921\) 年 \(7\) 月 \(23\) 日。
请问从 \(1921\) 年 \(7\) 月 \(23\) 日中午 \(12\) 时到 \(2020\) 年 \(7\) 月 \(1\) 日中午 \(12\) 时一共包含多少分钟?
【我的题解】
这题根本不用编程,附件里的计算器可以算日期差,然后乘 \(24\) 小时再乘 \(60\) 分钟就是答案了。
我的答案 \(52038720\)
试题 C: 合并检测
本题总分:10 分
【问题描述】
新冠疫情由新冠病毒引起,最近在 \(A\) 国蔓延,为了尽快控制疫情, \(A\) 国准备给大量民众进病毒核酸检测。
然而,用于检测的试剂盒紧缺。
为了解决这一困难,科学家想了一个办法:合并检测。即将从多个人( \(k\) 个)采集的标本放到同一个试剂盒中进行检测。如果结果为阴性,则说明这 \(k\) 个人都是阴性,用一个试剂盒完成了 \(k\) 个人的检测。如果结果为阳性,则说明至少有一个人为阳性,需要将这 \(k\) 个人的样本全部重新独立检测(从理论上看,如果检测前 \(k − 1\) 个人都是阴性可以推断出第 \(k\) 个人是阳性,但是在实际操作中不会利用此推断,而是将 \(k\) 个人独立检测),加上最开始的合并检测,一共使用了 \(k + 1\) 个试剂盒完成了 \(k\) 个人的检测。
\(A\) 国估计被测的民众的感染率大概是 \(1%\),呈均匀分布。请问 \(k\) 取多少能最节省试剂盒?
【我的题解】
当成有 \(100\) 人,其中有 \(1\) 个感染者来想。也就是求使得 \(\lceil \frac{100}{k} \rceil + k\) 最小的那个 \(k\),当 \(k\) 取 \(10\) 时 \(\lceil \frac{100}{k} \rceil + k\) 达到最小值。
我的答案 \(10\)
试题 D: REPEAT 程序
【问题描述】
附件 prog.txt 中是一个用某种语言写的程序。
其中 REPEAT k 表示一个次数为 k 的循环。循环控制的范围由缩进表达,从次行开始连续的缩进比该行多的(前面的空白更长的)为循环包含的内容。
例如如下片段:
REPEAT 2:
A = A + 4
REPEAT 5:
REPEAT 6:
A = A + 5
A = A + 7
A = A + 8
A = A + 9
该片段中从 A = A + 4 所在的行到 A = A + 8 所在的行都在第一行的循环两次中。
REPEAT 6: 所在的行到 A = A + 7 所在的行都在 REPEAT 5: 循环中。
A = A + 5 实际总共的循环次数是 2 × 5 × 6 = 60 次。
请问该程序执行完毕之后,A 的值是多少?
【我的题解】
用栈来控制每行前面的缩进,缩进多了就压栈,缩进少了就弹栈,维护一个循环次数。
#include
using namespace std;
typedef long long LL;
typedef pair PII;
const int MAXN = 2020;
char s[MAXN];
// a[i] -> 第 i 层循环的缩进,b[i] -> 第 i 层循环的循环次数
int a[MAXN], b[MAXN];
int main() {
freopen("prog.txt", "r", stdin);
int pos = 0, ans = 0, w = 1;
gets(s); // 读走第一行的 A = 0
a[pos] = -1, b[pos] = 1; // 防止在栈空的时候弹栈
while (gets(s)) {
int n = strlen(s), p = 0;
while (s[p] == ' ') p++; // 统计缩进
while (a[pos] >= p) w /= b[pos--];// 弹掉栈里缩进大于等于当前行的
if (s[n - 1] == ':') { // 当前行是循环,压栈
int k = s[n - 2] - '0';
pos = pos + 1;
w *= k;
a[pos] = p, b[pos] = k;
} else {
int k = s[n - 1] - '0';
ans += k * w;
}
}
printf("%d\n", ans);
return 0;
}
其实想来代码还是有漏洞的,毕竟题目没说所有数字都是在 \(1\) 到 \(9\) 之间,而且也没保证除了累加以外不会出现什么累乘之类的。不过粗略看了一下文件,大概不会出现上述问题吧。但愿出题人善良,别恶意搞我一手。而且看了看网上别人的答案好像也是这个。
我的答案 \(241830\)
试题 E: 矩阵
本题总分:15 分
【问题描述】
把 \(1 \sim 2020\) 放在 \(2 \times 1010\) 的矩阵里。要求同一行中右边的比左边大,同一列中下边的比上边的大。一共有多少种方案?
答案很大,你只需要给出方案数除以 \(2020\) 的余数即可。
【我的题解】
我们从 \(2020\) 个数里选 \(1010\) 个放入第一行,那么为了满足同一行中右边的比左边大,只能升序排列。同理剩下的 \(1010\) 个放入第二行的也要升序排列,那么只要对于任意 \(i \in [1, 1010]\) 都满足第二行第 \(i\) 个大于第一行第 \(i\) 个就是一种合法方案。从前往后枚举,用 \(dp[i][j]\) 表示当前枚举了 \(i\) 个数,其中 \(j\) 个放入第一行的合法方案数。
#include
using namespace std;
typedef long long LL;
typedef pair PII;
const int MAXN = 2030;
const int MOD = 2020;
int dp[MAXN][MAXN];
int main() {
int n = 2020;
dp[1][1] = 1; // 1必然放在第一行
for (int i = 2; i <= n; i++) {
for (int j = 1; j <= i; j++) {
dp[i][j] += dp[i - 1][j - 1]; // 将第i个数放第一行
if (i - j <= j) {
/*
因为是正向枚举,后面的数只会越来越大
要随时保持第一行的个数不能比第二行的少
否则必然出现这一列第二行比第一行小的情况
*/
dp[i][j] += dp[i - 1][j];
}
dp[i][j] %= MOD;
}
}
printf("%d\n", dp[2020][1010]);
return 0;
}
我的答案 \(1340\)
试题 F: 整除序列
时间限制: 1.0s 内存限制: 256.0MB 本题总分:15 分
【问题描述】
有一个序列,序列的第一个数是 \(n\),后面的每个数是前一个数整除 \(2\),请输出这个序列中值为正数的项。
【输入格式】
输入一行包含一个整数 \(n\)。
【输出格式】
输出一行,包含多个整数,相邻的整数之间用一个空格分隔,表示答案。
【评测用例规模与约定】
对于 \(80\%\) 的评测用例,\(1 \le n \le 10^{9}\)。
对于所有评测用例,\(1 \le n \le 10^{18}\)。
【我的题解】
只要稍微仔细点不栽在忘开 long long 上就没什么问题,签到题。
#include
using namespace std;
typedef long long LL;
typedef pair PII;
int main() {
long long n;
for (cin >> n; n != 0; n >>= 1) {
cout << n;
if (n != 1) putchar(' ');
else putchar('\n');
}
return 0;
}
试题 G: 解码
时间限制: 1.0s 内存限制: 256.0MB 本题总分:20 分
【问题描述】
小明有一串很长的英文字母,可能包含大写和小写。
在这串字母中,有很多连续的是重复的。小明想了一个办法将这串字母表达得更短:将连续的几个相同字母写成字母 \(+\) 出现次数的形式。
例如,连续的 $5 个 \(a\),即 \(aaaaa\),小明可以简写成 \(a5\)(也可能简写成 \(a4a\)、\(aa3a\) 等)。对于这个例子:\(HHHellllloo\),小明可以简写成 \(H3el5o2\)。为了方便表
达,小明不会将连续的超过 \(9\) 个相同的字符写成简写的形式。
现在给出简写后的字符串,请帮助小明还原成原来的串。
【输入格式】
输入一行包含一个字符串。
【输出格式】
输出一个字符串,表示还原后的串。
【评测用例规模与约定】
对于所有评测用例,字符串由大小写英文字母和数字组成,长度不超过 \(100\)。
请注意原来的串长度可能超过 \(100\)。
【我的题解】
#include
using namespace std;
typedef long long LL;
typedef pair PII;
const int MAXN = 110;
char s[MAXN];
int main() {
scanf("%s", s);
for (int i = 0; s[i]; i++) {
if (s[i] >= 'a' && s[i] <= 'z') {
putchar(s[i]);
} else if (s[i] >= 'A' && s[i] <= 'Z') {
putchar(s[i]);
} else {
int k = s[i] - '0' - 1;
while (k--) putchar(s[i - 1]);
}
}
puts("");
return 0;
}
试题 H: 走方格
时间限制: 1.0s 内存限制: 256.0MB 本题总分:20 分
【问题描述】
在平面上有一些二维的点阵。
这些点的编号就像二维数组的编号一样,从上到下依次为第 \(1\) 至第 \(n\) 行,从左到右依次为第 \(1\) 至第 \(m\) 列,每一个点可以用行号和列号来表示。
现在有个人站在第 \(1\) 行第 \(1\) 列,要走到第 \(n\) 行第 \(m\) 列。只能向右或者向下走。
注意,如果行号和列数都是偶数,不能走入这一格中。
问有多少种方案。
【输入格式】
输入一行包含两个整数 \(n\), \(m\)。
【输出格式】
输出一个整数,表示答案。
【评测用例规模与约定】
对于所有评测用例,\(1 \le n \le 30, 1 \le m \le 30\)。
【我的题解】
这种走二维平面的题总是会先想到深搜,敲完了输入极限数据跑了好几秒,然后就改成了动规。
#include
using namespace std;
typedef long long LL;
typedef pair PII;
const int MAXN = 40;
int dp[MAXN][MAXN];
int main() {
int n, m;
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (i == 1 && j == 1) {
dp[i][j] = 1;
continue;
}
if ((i & 1) || (j & 1)) {
dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
}
}
}
printf("%d\n", dp[n][m]);
return 0;
}
试题 I: 整数拼接
时间限制: 1.0s 内存限制: 256.0MB 本题总分:25 分
【问题描述】
给定义个长度为 \(n\) 的数组 \(A_{1}, A_{2}, · · · , A_{n}\)。你可以从中选出两个数 \(A_{i}\) 和 \(A_{j}\) ( \(i\) 不等于 \(j\) ),然后将 \(A_{i}\) 和 \(A_{j}\) 一前一后拼成一个新的整数。例如 \(12\) 和 \(345\) 可以拼成 \(12345\) 或 \(34512\)。注意交换 \(A_{i}\) 和 \(A_{j}\) 的顺序总是被视为 \(2\) 种拼法,即便是 \(A_{i} = A_{j}\) 时。
请你计算有多少种拼法满足拼出的整数是 K 的倍数。
【输入格式】
第一行包含 \(2\) 个整数 \(n\) 和 \(K\)。
第二行包含 \(n\) 个整数 \(A_{1}, A_{2}, · · · , A_{n}\)。
【输出格式】
一个整数代表答案。
【评测用例规模与约定】
对于 \(30\%\) 的评测用例,\(1 \le n \le 1000, 1 \le K \le 20, 1 \le A_{i} \le 10^{4}\)。
对于所有评测用例,\(1 \le n \le 10^{5},1 \le K \le 10^{5},1 \le A_{i} \le 10^{9}\)。
【我的题解】
用 \(cnt[i][j]\) 记录在之前的数里,在后面补 \(i\) 个 \(0\) 对 \(K\) 取余结果为 \(j\) 的数的个数。 对于某个数 \(n\),累加 \(cnt[len(n)][(K - (n \% K)) \% K]\)。
分别从前往后跑一遍,从后往前跑一遍并统计就好了。
#include
using namespace std;
typedef long long LL;
typedef pair PII;
const int MAXN = 100010;
int cnt[10][MAXN];
int a[MAXN];
int get_len(int n) {
int t = 0;
while (n) {
n /= 10;
t += 1;
}
return t;
}
int main() {
int n, m;
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
}
int ans = 0;
memset(cnt, 0, sizeof(cnt));
for (int i = 1; i <= n; i++) {
int t = get_len(a[i]);
int k = (m - a[i] % m) % m;
ans += cnt[t][k];
int p = 10;
for (int j = 1; j <= 9; j++) {
cnt[j][1LL * p * a[i] % m] ++;
p = 10 * p;
}
}
memset(cnt, 0, sizeof(cnt));
for (int i = n; i >= 1; i--) {
int t = get_len(a[i]);
int k = (m - a[i] % m) % m;
ans += cnt[t][k];
int p = 10;
for (int j = 1; j <= 9; j++) {
cnt[j][1LL * p * a[i] % m] ++;
p = 10 * p;
}
}
printf("%d\n", ans);
return 0;
}
试题 J: 网络分析
时间限制: 1.0s 内存限制: 256.0MB 本题总分:25 分
【问题描述】
小明正在做一个网络实验。
他设置了 \(n\) 台电脑,称为节点,用于收发和存储数据。
初始时,所有节点都是独立的,不存在任何连接。
小明可以通过网线将两个节点连接起来,连接后两个节点就可以互相通信了。两个节点如果存在网线连接,称为相邻。
小明有时会测试当时的网络,他会在某个节点发送一条信息,信息会发送到每个相邻的节点,之后这些节点又会转发到自己相邻的节点,直到所有直接或间接相邻的节点都收到了信息。所有发送和接收的节点都会将信息存储下来。一条信息只存储一次。
给出小明连接和测试的过程,请计算出每个节点存储信息的大小。
【输入格式】
输入的第一行包含两个整数 \(n, m\),分别表示节点数量和操作数量。节点从
\(1\) 至 \(n\) 编号。
接下来 \(m\) 行,每行三个整数,表示一个操作。
如果操作为 \(1\ a\ b\),表示将节点 \(a\) 和节点 \(b\) 通过网线连接起来。当 \(a = b\) 时,表示连接了一个自环,对网络没有实质影响。
如果操作为 \(2\ p\ t\),表示在节点 \(p\) 上发送一条大小为 \(t\) 的信息。
【输出格式】
输出一行,包含 \(n\) 个整数,相邻整数之间用一个空格分割,依次表示进行
完上述操作后节点 \(1\) 至节点 \(n\) 上存储信息的大小。
【评测用例规模与约定】
对于 \(30\%\) 的评测用例,\(1 \le n \le 20,1 ≤ m \le 100\)。
对于 \(50\%\) 的评测用例,\(1 \le n \le 100,1 ≤ m \le 1000\)。
对于 \(70\%\) 的评测用例,\(1 \le n \le 1000,1 ≤ m \le 10000\)。
对于所有评测用例,\(1 \le n \le 10000,1 \le m \le 100000,1 \le t \le 100\)。
【我的题解】
连接两个连通块,很容易想到并查集,但是比赛的时候没有想到如何比较好的解决整个连通块加上一个 \(t\),所以就暴力枚举所有节点,如果和 \(p\) 属于一个连通块就加 \(t\),对标70分的做法。赛后好像想明白怎么解决整个连通块的修改了,还是类似线段树懒标记先把修改存到连通块的根上面之后再往下传递。
#include
using namespace std;
typedef long long LL;
typedef pair PII;
const int MAXN = 10010;
int dsu[MAXN], mark[MAXN];
int find(int u) {
if (dsu[u] == 0) return u;
int fu = find(dsu[u]);
if (dsu[u] != fu) { // 没有直接连在根上
mark[u] += mark[dsu[u]]; // 把父亲那的数据懒标记下传
dsu[u] = fu; // 把根设为父亲, 状态压缩
}
return fu;
}
int main() {
int n, m;
scanf("%d%d", &n, &m);
for (int i = 1; i <= m; i++) {
int op, a, b;
scanf("%d%d%d", &op, &a, &b);
if (op == 1) {
int fa = find(a);
int fb = find(b);
if (fa != fb) {
dsu[fa] = fb;
/*
由于fa接到了fb上,fb的mark之后会传递给fa
但是这部分数据是fb独有的,不该传给fa
所以事先在fa里减掉一个mark[fb]
之后mark[fb]传回来才能保持不受影响
*/
mark[fa] -= mark[fb];
}
} else {
int fa = find(a);
mark[fa] += b;
}
}
for (int i = 1; i <= n; i++) {
int res = mark[i];
int fi = find(i);
if (fi != i) res += mark[fi]; // 自己不是根,就说明有部分数据在根上还没传下来
printf("%d%c", res, " \n"[i == n]);
}
return 0;
}
总结
之前蓝桥杯被称为暴力杯,题目都挺暴力的,基本上每场都有暴力全排列或者暴力搜索的题,next_permutation非常香。从去年开始,感觉题目风格突变,开始往ACM上靠了。这次省赛更是两道动态规划一道并查集。发挥上感觉也还行,目前还没看到什么大失误,希望达到预期吧。