第八届传智杯初赛第二场题解
第一题:
【回忆题目】 已知今天是个星期 a a a( 1 ≤ a ≤ 7 1 \le a \le 7 1≤a≤7),请问经过 b b b( b ≥ 0 b \ge 0 b≥0)天后,是星期几?
结果 = ( 当前星期几 + 经过天数 − 1 ) m o d 7 + 1 \text{结果} = (\text{当前星期几} + \text{经过天数} - 1) \bmod 7 + 1 结果=(当前星期几+经过天数−1)mod7+1
AC代码:c语言
#include <stdio.h>
int main() {
long long a, b;
scanf("%lld %lld", &a, &b);
printf("%lld\n", (a + b - 1) % 7 + 1);
return 0;
}
第二题:
【回忆题目】 你有 n n n 个水果,每个水果都有两个属性:酸度和甜度。现在你需要从中挑选出 k k k 个水果。挑选的规则如下:使得选出的 k k k 个水果的甜度总和达到最大。在满足甜度总和最大的前提下,使得这 k k k 个水果的酸度总和尽可能小。
请输出最终挑选出的 k k k 个水果的甜度总和及酸度总和。
AC代码:c语言
解法一:自定义排序
如果甜度不同,按甜度降序排列 (从大到小)
如果甜度相同,按酸度升序排列 (从小到大)
#include <stdio.h>
#include <stdlib.h>
typedef struct {
int acid;
int sweet;
} Fruit;
Fruit fruits[200005];
// 排序逻辑
int cmp(const void *a, const void *b) {
Fruit *f1 = (Fruit *)a;
Fruit *f2 = (Fruit *)b;
// 甜度升序
if (f1->sweet != f2->sweet) {
return f1->sweet - f2->sweet;
}
// 甜度相同时,酸度降序
return f2->acid - f1->acid;
}
int main() {
int n, k;
scanf("%d %d", &n, &k);
for (int i = 0; i < n; i++) {
scanf("%d", &fruits[i].acid);
}
for (int i = 0; i < n; i++) {
scanf("%d", &fruits[i].sweet);
}
qsort(fruits, n, sizeof(Fruit), cmp);
long long sum_sweet = 0, sum_acid = 0;
for (int i = 0; i < k; i++) {
sum_sweet += fruits[i].sweet;
sum_acid += fruits[i].acid;
}
printf("%lld %lld\n", sum_sweet, sum_acid);
return 0;
}
解法二:排序+滑动窗口
最简单解法为解法一,解法二可自行了解
第三题:
【回忆题目】 给定 T T T 组测试数据,每组包含一个字符串。对于字符串中的第 i i i 个字符(下标从 1 开始),如果 i i i 的二进制表示中包含奇数个 1,则将该字符转换为大写字母;否则保持不变。字符串长度 1 < = l e n < = 500 1<=len<=500 1<=len<=500
AC代码:c语言
解法一:预处理+模拟
本题 T T T 组样例容易超时,所以预处理空间换时间
#include <stdio.h>
#include <string.h>
int needs_upper[505];
// 常规方案
int count_ones(int n) {
int c = 0;
while (n > 0) {
c += (n & 1); // 如果最低位是 1,计数加 1
n >>= 1; // 右移一位,检查下一位
}
return c;
}
// 优化方案:Brian Kernighan 算法,最好记住
// int count_ones(int n) {
// int count = 0;
// while (n > 0) {
// n &= (n - 1);
// count++;
// }
// return count;
// }
void init() {
for (int i = 1; i <= 500; i++) {
if (count_ones(i) % 2 != 0) {
needs_upper[i] = 1; // 奇数个 1,标记为需要转换
} else {
needs_upper[i] = 0;
}
}
}
int main() {
init();
int t;
scanf("%d", &t);
char s[505];
while (t--) {
scanf("%s", s);
int len = strlen(s);
for (int i = 1; i <= len; i++) {
// 直接查表,时间复杂度 O(1)
if (needs_upper[i]) {
if (s[i-1] >= 'a' && s[i-1] <= 'z') {
s[i-1] -= 32;
}
// 直接用库函数
// s[i-1] = toupper(s[i-1]);
}
}
printf("%s\n", s);
}
return 0;
}
第四题:
【回忆题目】 给定一个长度为 n n n 的字符串,如果将其看作一个环(最后一个字符和第一个字符相邻),求出由相同字符组成的最长子串的长度。
AC代码:c语言
解法一:双指针
#include <stdio.h>
#include <string.h>
int main() {
char s[1000005];
int n, l = 0, r, cnt, ans = 0;
scanf("%s", s);
n = strlen(s);
while (l < n) {
r = l; // 右指针从左指针位置开始
cnt = 0; // 计数当前段长度
// r 指针通过取模 % n 向后探索
// 限制 cnt < n 防止全同字符导致死循环
while (cnt < n && s[r % n] == s[l]) {
r++; // r 持续向右移动
cnt++; // 计数增加
}
// 更新最大长度
if (cnt > ans) ans = cnt;
// 如果已经达到最大可能长度(全串相同),直接退出
if (ans == n) break;
// 左指针跳跃到右指针当前指向的不同字符位置
// 注意:由于是取模逻辑,l 直接增加 cnt 即可
l += cnt;
}
printf("%d\n", ans);
return 0;
}
第五题:
【回忆题目】 给定一个含有 n n n 个正整数的数组 A = [ a 1 , a 2 , … , a n ] A = [a_1, a_2, \dots, a_n] A=[a1,a2,…,an]。你可以选择一个非负整数 x x x,并将数组中的每个元素都加上 x x x,得到新数组 A ′ = [ a 1 + x , a 2 + x , … , a n + x ] A' = [a_1+x, a_2+x, \dots, a_n+x] A′=[a1+x,a2+x,…,an+x]。 你的目标是找到一个 x x x,使得新数组所有元素的最大公约数 (GCD) 达到最大。如果存在多个这样的 x x x,请求出最小的那个。
思路:
直接通过循环累加 g g g 在数学逻辑上是行不通的,因为当数组中的数很大时,单纯的模拟会导致超时。我们需要利用 GCD 的一个关键性质:
性质: gcd ( x , y ) = gcd ( x , y − x ) \gcd(x, y) = \gcd(x, y - x) gcd(x,y)=gcd(x,y−x)
推广到多个数:
gcd
(
a
1
,
a
2
,
a
3
,
…
,
a
n
)
=
gcd
(
a
1
,
a
2
−
a
1
,
a
3
−
a
2
,
…
,
a
n
−
a
n
−
1
)
\gcd(a_1, a_2, a_3, \dots, a_n) = \gcd(a_1, a_2-a_1, a_3-a_2, \dots, a_n-a_{n-1})
gcd(a1,a2,a3,…,an)=gcd(a1,a2−a1,a3−a2,…,an−an−1)
当我们给所有数都加上
x
x
x 后,数组变为
a
i
+
x
a_i + x
ai+x。此时它们的 GCD 为:
G
=
gcd
(
a
1
+
x
,
(
a
2
+
x
)
−
(
a
1
+
x
)
,
(
a
3
+
x
)
−
(
a
2
+
x
)
,
…
)
G = \gcd(a_1+x, (a_2+x)-(a_1+x), (a_3+x)-(a_2+x), \dots)
G=gcd(a1+x,(a2+x)−(a1+x),(a3+x)−(a2+x),…)
简化后:
G
=
gcd
(
a
1
+
x
,
a
2
−
a
1
,
a
3
−
a
2
,
…
,
a
n
−
a
n
−
1
)
:
G = \gcd(a_1+x, a_2-a_1, a_3-a_2, \dots, a_n-a_{n-1}):
G=gcd(a1+x,a2−a1,a3−a2,…,an−an−1):
除了第一项 a 1 + x a_1+x a1+x 以外,后面所有的差值(如 a 2 − a 1 a_2-a_1 a2−a1)都与 x x x 无关。这意味着无论你加多少, G G G 永远是这些差值的公约数。
为了让
G
G
G 最大,最好的情况就是让
G
G
G 等于所有差值的最大公约数。令
g
d
i
f
f
=
gcd
(
∣
a
2
−
a
1
∣
,
∣
a
3
−
a
2
∣
,
…
,
∣
a
n
−
a
n
−
1
∣
)
g_{diff} = \gcd(|a_2-a_1|, |a_3-a_2|, \dots, |a_n-a_{n-1}|)
gdiff=gcd(∣a2−a1∣,∣a3−a2∣,…,∣an−an−1∣)
我们要找到最小的非负整数
x
x
x,使得第一个元素
a
1
+
x
a_1 + x
a1+x 能被
g
d
i
f
f
g_{diff}
gdiff 整除。
利用取模运算: a 1 ( m o d g d i f f ) a_1 \pmod{g_{diff}} a1(modgdiff),如果余数为 0, x = 0 x = 0 x=0,如果余数不为 0, x = g d i f f − ( a 1 ( m o d g d i f f ) ) x = g_{diff} - (a_1 \pmod{g_{diff}}) x=gdiff−(a1(modgdiff))。
AC代码:c语言
#include <stdio.h>
long long gcd(long long a, long long b) {
return b == 0 ? a : gcd(b, a % b);
}
int main() {
int n;
long long first, current, g = 0;
scanf("%d %lld", &n, &first);
// 循环处理后续数字,节省内存
for (int i = 1; i < n; i++) {
scanf("%lld", ¤t);
// 计算差值的绝对值
long long diff = (current > first) ? (current - first) : (first - current);
g = gcd(g, diff);
}
// 计算最小加数 x,使得 (first + x) % g == 0
long long x = (g - (first % g)) % g;
printf("%lld %lld\n", g, x);
return 0;
}
第六题:
【回忆题目】 给定一个整数 n n n,代表楼梯的总阶数。你从第 0 阶出发,向上爬到第 n n n 阶。左脚只能踩在奇数阶,右脚只能踩在偶数阶;必须左右脚交替行进(即不能连续用同一只脚迈步);第一步可以迈左脚或右脚。
计算到达第 n n n 阶的所有可能走法的总数,结果对 1 0 9 + 7 10^9 + 7 109+7 取模。
解法一:动态规划
解法二:矩阵快速幂
先列出解法,后续补充思路和代码
4831

被折叠的 条评论
为什么被折叠?



