第十六届蓝桥杯模拟赛(第一期)(C语言)
判断质因数
如果一个数p是个质数,同时又是整数a的约数,则p称为a的一个质因数。
请问2024有多少个质因数。
- 了解
- 约数,又称因数。整数a整除整数b,b为a的因数(约数)
- 质数,又称素数。只有1和它本身两个因数,即只能被1和它本身整除
- 考察:循环枚举2024的每一个因数,并且判断是否为质数
思路
使用平方根判断法(判断是否为质数)
- 因为 n = a * b,这两个都为 x 的因数
- 当 a < sqrt(n) 时,则有 b * a = n(b > sqrt(n))
- 当 a >= sqrt(n) 时,则有 b * a = n (b <= sqrt(n))
- 所以判断时,变量 i 从 2 开始 只需要循环到 sqrt(n) 即可
代码
#include <bits/stdc++.h>
// 判断是否为质数
// 平方根判断法
//check,检查是否为质数
int chk(int x) {
for(int i = 2; i <= sqrt(x); ++i) {
if (x % i == 0) {
return 0;
}
}
return 1;
}
int main() {
int ans = 0;
// 检查是否为因数
for (int i = 2; i < 2024; ++i) {
if (2024 % i == 0) ans += chk(i);
}
printf("%d\n", ans);
return 0;
}
答案
3
分析
通过检查2024的所有可能的因数,然后判断这些因数是否为质数,来找出2024的所有质因数
for (int i = 2; i < 2024; ++i) {
if (2024 % i == 0) ans += chk(i);
}
- 检查 i 是否为 2024 的因数,如果 i 能被 2024 整除,就是
int chk(int x) {
for(int i = 2; i <= sqrt(x); ++i) {
if (x % i == 0) {
return 0;
}
}
return 1;
}
- 当 x 不为素数,return 0; 连同 for 循环停止,继续对下一个 2024 的因数判断
- 当 x 为素数,循环一直通过,直到结束,return 1;
开根次方
对于一个整数 n,我们定义一次开根变换会将 n 变为开根号后的整数部分,即变为平方和不超过 n 的数中的最大数
例如,20 经过开根后变为 4,如果再经过一次开根变换将变为 2,如果再经过一次开根变换将变为 1
请问,2024 经过多少次开根变换后会变为 1
- 考察:循环、模拟、整数截断
思路
- 用一个变量记录开了几次根,用强制类型转换把进行整数截断
代码
#include <bits/stdc++.h>
int main() {
int n = 2024;
int ans = 0;
while (n > 1) {
ans++;
n = int(sqrt(n));
}
printf("%d\n", ans);
return 0;
}
答案
4
补立方体
小蓝有很多 1x1x1 的小立方体,他可以使用多个立方体拼成更大的立方体。
例如,小蓝可以使用 8 个小立方体拼成一个大立方体,每边都是 2 个。
又如,小蓝可以使用 27 个小立方体拼成一个大立方体,每边都是 3 个。
现在,小蓝有 2024 个小立方体,他想再购买一些小立方体,用于拼一个超大的立方体,要求所有的小立方体都用上,拼成的大立方体每边长度都相等。
请问,小蓝最少需要购买多少个小立方体?
思路
- 找到一个数的立方,大于等于 2024
- 然后这个立方数减去 2024,就是还缺少的小立方体数
代码
#include <bits/stdc++.h>
int main() {
int n = 2024;
for (int i = 1; ; ++i) {
if (i*i*i >= n) {
printf("%d\n", i*i*i - n) ;
return 0;
}
}
return 0;
}
答案
173
一好日期
如果一个日期的日期以 1 结尾(1日、11日、21日、31日)且为星期一,则称这个日期为一好日期。
请问从 1901 年 1 月 1 日至 2024 年 12 月 31 日总共有多少个一好日期。
提示:1901 年 1 月 1 日是星期二、
- 考察:循环,模拟,闰年的判断方法
思路
- 枚举年月日,判断星期
判断星期
- 一星期 7 天,对 7 进行取余
- 如果余 1,则为星期一
- ……
- 如果余 0,则为星期天
week = (week + 1) % 7;
判断闰年
- 能被4整除而不能被100整除,或者能被400整除的年份都是闰年
代码
#include <bits/stdc++.h>
int main() {
// 平年12个月,每月有多少天
int day_12_months[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
int week = 2;
int ans = 0;
// 对年进行循环
for (int year = 1901; year <= 2024; ++year) {
// 对月进行循环,考虑2月
for (int month = 1; month <= 12; ++month) {
int day_1_month = day_12_months[month - 1]; // 具体某一月的天数
// 如果是闰年的2月,天数+1
if ((month == 2) &&
((year % 4 == 0 && year % 400 != 0) || year % 400 == 0))
day_1_month++;
// 对天进行循环
for (int day = 1; day <= day_1_month; ++day) {
if (day % 10 == 1 && week == 1) { // 判断一好日期
ans++;
}
week = (week + 1) % 7; // 判断星期
}
}
}
printf("%d\n", ans) ;
return 0;
}
答案
762
特殊的V
两个数按位异或是指将这两个数转换成二进制后,最低位与最低位异或作为结果的最低位,次低位与次低位异或作为结果的次低位,以此类推。
例如,3 与 5 按位异或值为 6 。
小蓝有以下 30 个整数:
9226, 4690, 4873, 1285, 4624, 1596, 6982, 590, 8806, 121, 8399, 8526, 5426, 64, 9655, 7705, 3929, 3588, 7397, 8020, 1311, 5676, 3469, 2325, 1226, 8203, 9524, 3648, 5278, 8647
小蓝想找一个整数 V ,使得 V 与这 30 个数分别异或后,得到的 30 个数的平方和最小。请问平方和最小是多少?
- 考察位运算
思路
异或操作符(xor) ^
- 以两进制表示数字,通过异或操作符,二进制位上,相同为 0,不同为 1
- 最大值是9665没有超过15位,所以我们可以枚举到2¹⁵即可
- 计算器 9665 的二进制为 0010 0101 1100 0001
- 用 i 进行遍历,上限是 2¹⁵,用左移操作符,1 << 15表示
- 1 的二进制数为 0001,末位的 1 向左移 15 位
代码
#include <stdio.h>
int a[] = {9226, 4690, 4873, 1285, 4624, 1596, 6982, 590, 8806,
121, 8399, 8526, 5426, 64, 9655, 7705, 3929, 3588, 7397, 8020,
1311, 5676, 3469, 2325, 1226, 8203, 9524, 3648, 5278, 8647};
int main() {
// 赋值一个绝对比答案大的数
long long ans = 1000000000000000000LL;
// 1左移15位就是2的15次
for (int i = 0; i < (1 << 15); ++i) {
long long tmp = 0;
for (int j = 0; j < 30; ++j) {
tmp += (a[j] ^ i) * (a[j] ^ i);
}
ans = ans > tmp ? tmp : ans ;
}
printf("%lld", ans);
return 0;
}
答案
1070293541
停车收费
- 考察:模拟、整除
【问题描述】
小蓝在一个停车场停车。停车场的收费规则为:每 15 分钟收费 2 元,不满 15 分钟的不收费。
小蓝总共停车 n 分钟,请问收费总额是多少?
【输入格式】
输入一行包含一个整数 n ,表示小蓝停车的时长。【输出格式】
输出一行包含一个整数,表示停车费用。【样例输入】
150【样例输出】
20【样例输入】
2024【样例输出】
268【评测用例规模与约定】
对于所有评测用例,1 <= n <= 10000。
思路
- 求 n 分钟里包含多少 15 分钟
代码
#include <stdio.h>
#include <math.h>
int main() {
int n;
scanf("%d", &n);
printf("%d\n", n / 15 * 2);
return 0;
}
数位减法
- 考察:思维、整数分解、循环
小蓝有一个整数 n ,每次操作,可以将这个整数的每个非零数位减少 1 。
请问经过多少次操作,这个数会变为 0 。
例如,整数 2024 经过一次操作变为 1013,再经过一次操作变为 2 (即0002),再经过两次操作变为 0 ,总共经过 4 次变换变为 0 。
【输入格式】
输入一行包含一个整数 n 。【输出格式】
输出一行,包含一个整数,表示答案。【样例输入】
2024【样例输出】
4【评测用例规模与约定】
对于 50% 评测用例,1 <= n < 10000对于所有评测用例,1 <= n < 1000000000
思路
- 答案就是数位中的最大值
代码
#include <stdio.h>
int main() {
int n;
int ans = 0;
scanf("%d", &n);
while(n > 0) {
int tmp = n % 10;
n /= 10;
ans = ans < tmp ? tmp : ans;
}
printf("%d\n", ans);
return 0;
}
指数计算
- 考察:模拟、取模、循环
输入 n,请计算 n 的 n 次方。答案可能很大,请输出答案除以 2024 的余数
【输入格式】
输入一行包含一个整数 n
【输出格式】
输出一行,包含一个整数,表示 n 的 n 次方除以 2024 的余数
【样例输入】
10
【样例输出】
936
【评测用例规模与约定】
对于 20% 的评测用例,1 <= n <= 10
对于 60% 的评测用例,1 <= n <= 100
对于所有评测用例,1 <= n <= 1000000
思路
- 不断地对 ans 乘以 n,乘以 n 次
- 要一边乘,一边取模
- long long 最大 2⁶⁴ 左右,也就是 10¹⁸ ~ 10¹⁹ 左右,而评测用例中,如过 n = 1000000,3次左右就到上限了,所以不能直接 n * n
- 使用快速幂和整数取模的性质关于取模和快速幂的笔记
代码
#include <stdio.h>
// 快速幂算法
long long fastPower(long long base, long long exponent, long long mod) {
long long ans = 1;
base = base % mod;
while (exponent > 0) {
if (exponent & 1) {
ans = (ans * base) % mod;
}
exponent = exponent >> 1;
base = (base * base) % mod;
}
return ans;
}
int main() {
long long n;
scanf("%lld", &n);
printf("%lld\n", fastPower(n, n, 2024));
return 0;
}
减法求值
小蓝有一个减法式子,形如 a-b,其中 a 和 b 都是非负整数(不保证结果非负)。
请编程处理这个式子,输出运算结果。
【输入格式】
输入一行包含一个减法表达式,式子中仅含数字字符和一个减号。【输出格式】
输出一行包含一个整数,表示运算结果。【样例输入】
2024-1949【样例输出】
75【样例输入】
20-24【样例输出】
-4【评测用例规模与约定】
对于 50% 的评测用例,减法中的两个数都是不超过 10000 的非负整数。对于所有评测用例,减法中的两个数都是不超过 1000000000 的非负整数。
代码
#include <stdio.h>
int main() {
int a, b;
scanf("%d-%d", &a, &b) ;
printf("%d\n", a-b);
}
间隔最大
- 考察前缀和,分类讨论
- 将偶数和奇数分开考虑
- 对奇数和偶数分别使用前缀和预处理,在获取某个长度为 k 的子段时,直接利用前缀和处理即可
小蓝有一个长度为 n 的整数数列 a[1], a[2], ..., a[n] 。
对于一个给定的整数 k ,小蓝想找到相邻间隔为 1 的 k 个数 a[p], a[p+2], a[p+4], ..., a[p+2k-2],使得他们的和最大。其中 1 <= p <= n-2k+2。
给定数列和 k ,请问给出最大的和。
【输入格式】
输入的第一行包含一个整数 n 。第二行包含 n 个整数,相邻数之间使用一个空格分隔,依次表示 a[1], a[2], ..., a[n] 。
第三行包含一个整数 k 。
【输出格式】
输出一行,包含一个整数,表示答案。【样例输入】
10 2 1 4 7 4 8 3 6 4 7 2
【样例输出】
15
【样例说明】
取 p=4,a[4]+a[6]=7+8=15 最大。【评测用例规模与约定】
对于 30% 的评测用例,1 <= k <= n <= 100 , 1 <= a[i] <= 10000 。对于 60% 的评测用例,1 <= k <= n <= 3000 , 1 <= a[i] <= 10000 。
对于所有评测用例,1 <= k <= n <= 100000 , 1 <= a[i] <= 1000000 。
思路
-
首先求出从数组下标1开始,往后k个奇数下标位置的和与偶数下标位置的和。
-
接着以长度为k个数,向后滑动,每移动一个,让和减去最前面的数,加上最后面的新进来的数。
变成了求连续的 k 个数的和 -> 前缀和
代码
#include <stdio.h>
#include <math.h>
#define N 100000
long long odd[N + 1], even[N + 1];
int main() {
int n;
scanf("%d", &n);
int oddn = 0, evenn = 0; // 奇数位置的个数,偶数位置的个数
long long ans = 0;
for (int i = 1; i <= n; ++i) {
if (i % 2 == 1) {
oddn++;
scanf("%lld", &odd[oddn]);
odd[oddn] += odd[oddn - 1]; // 奇数的前缀和
} else {
evenn++;
scanf("%lld", &even[evenn]);
even[evenn] += even[evenn - 1]; //偶数的前缀和
}
}
int k;
scanf("%d", &k);
for (int i =k; i <= oddn; ++i) { // 处理奇数
if (odd[i] - odd[i - k] > ans) {
ans = odd[i] - odd[i - k] ;
}
}
for (int i = k; i <= evenn; ++i) { // 处理偶数
if (even[i] - even[i - k] > ans) {
ans = even[i] - even[i - k];
}
}
printf("%lld", ans);
return 0;
}
分析
-
理解问题:我们需要找到一种方式来最大化间隔为 1 的 k 个数的和。这意味着我们需要考虑两种情况:一种是从奇数位置开始取数,另一种是从偶数位置开始取数。
-
前缀和:为了快速计算任意区间的和,我们可以使用前缀和。前缀和是一种数据结构,它允许我们在 O(1) 时间内计算任意区间的和。对于奇数位置和偶数位置的数,我们分别计算它们的前缀和。
-
奇偶分离:我们将数列中的奇数位置和偶数位置的数分别存储在两个数组
odd
和even
中,并计算它们的前缀和。 -
遍历求最大和:对于奇数位置和偶数位置的前缀和数组,我们分别遍历,计算从第 k 个数开始的 k 个数的和,并与当前的最大和
ans
比较,如果更大,则更新ans
。 -
输出结果:最后输出找到的最大和。
具体来说,如果数列中的所有数都是负数,那么最大和可能是这些负数中最大的一个(即最不负面的数),这个值可能小于或等于 0。在这种情况下,如果我们的 ans
是 0,我们就无法正确地识别出最大和,因为 0 已经被设置为初始的最大值。
因此,为了确保我们能够找到真正的最大和,无论数列中的数是正是负,将 ans
初始化为 INT_MIN
是一个更安全的选择。这样,任何实际的和都会大于 INT_MIN
,确保我们能够正确地更新 ans
并最终找到最大和。
- 判断当前元素的位置
i
是否为奇数(使用i % 2 == 1
判断) - 如果是奇数:
- 将
oddn
(奇数位置的个数)加 1 - 将当前元素
num
加到odd[oddn - 1]
(前一个奇数位置的前缀和)上,得到新的前缀和,存储到odd[oddn]
中
个
- 循环从 k 开始,到
oddn
结束,oddn
是奇数位置的个数。 - 对于每个奇数位置 i,计算从位置 i 开始的 k 个奇数位置的数的和,这可以通过
odd[i] - odd[i - k]
得到。这里odd[i]
是到位置 i 的所有奇数位置的前缀和,odd[i - k]
是到位置 i - k 的所有奇数位置的前缀和,它们的差就是从位置 i - k + 1 到位置 i 的 k 个奇数位置的数的和。 - 如果这个和大于当前的最大和
ans
,则更新ans
。
最大的勾
- 考察动态规划、枚举
小蓝有一个长度为 n 的整数序列 a[1], a[2], …, a[n] 。
他希望从中找出一个最长的子序列,形成一个勾的形状(√)。
即找到 1 <= p[1] < p[2] < … < p[k] <= n,满足 a[p[1]] > a[p[2]] > a[p[3]] > … > a[p[x]] < a[p[x+1]] < … < a[p[k]] 。其中 k 是子序列的长度,x 是勾中最小的位置。目标是使得 k 最大。
请找出最大的勾的长度。
【输入格式】
输入的第一行包含一个整数 n 。第二行包含 n 个整数,相邻数之间使用一个空格分隔,依次表示 a[1], a[2], …, a[n] 。
【输出格式】
输出一行,包含一个整数,表示答案。【样例输入】
10 2 1 4 7 4 8 3 6 4 7
【样例输出】
5
【样例说明】
当 p = (4,5,7,9,10) 时,a[4] , a[5] , a[7] , a[9] , a[10] 可形成一个长度为 5 的勾:7,4,3,6,7【评测用例规模与约定】
对于 30% 的评测用例,1 <= n <= 20 , 1 <= a[i] <= 100 。对于 60% 的评测用例,1 <= n <= 100 , 1 <= a[i] <= 1000 。
对于所有评测用例,1 <= n <= 1000 , 1 <= a[i] <= 10000 。
思路
- 要求一个数最小,之后就是上升的样子,就成了“勾”
- 首先将问题分解,将“勾”的左边与右边进行分解
- 可以得到两个最长上升子序列问题
- 然后将两个上升子学术论文合并起来即可
- 所以我们枚举“勾”的底部,得到前缀的最长下降子序列,后缀的最长上升子序列即可
代码
#include <stdio.h>
#define N 1010
int a[N];
int dppre[N], dpsuf[N];
int main() {
int n;
scanf("%d", &n);
for (int i = 1; i <= n; ++i) {
scanf("%d", &a[i]);
}
for (int i = 1; i <= n; ++i) {
dppre[i] = 0; // 初始化
for (int j = 1; j < i; ++j) {
if (a[i] < a[j]) {
if (dppre[j] + 1 > dppre[i]) {
dppre[i] = dppre[j] + 1;
}
}
}
}
for (int i = n; i > 0; --i) {
dpsuf[i] = 0; // 初始化
for (int j = i + 1; j <= n; ++j) {
if (a[i] < a[j]) {
if (dpsuf[j] + 1 > dpsuf[i]) {
dpsuf[i] = dpsuf[j] + 1;
}
}
}
}
int ans = 0;
for (int i = 2; i < n; ++i) {
if (dppre[i] > 0 && dpsuf[i] > 0 && dppre[i] + dpsuf[i] + 1 > ans) {
ans = dppre[i] + dpsuf[i] + 1;
}
}
printf("%d\n", ans);
return 0;
}