喵哈喽~!各位主人,欢迎来到我的题解小课堂~ 今天我们要看的是一道可爱的数学题,Codeforces 1883C - Raspberries。别看它叫树莓,其实和树莓一点关系都没有哦,噗嗤~ 让我们一起来看看怎么用最少的力气解决它吧!喵~
题目大意
这道题是这样哒:
我们有一个装着 n
个整数的数组 a
,还有一个整数 k
(k
的值只会在 2, 3, 4, 5 之间哦)。我们可以进行一种操作:选择数组里的任意一个数 a_i
,然后把它变成 a_i + 1
。这个操作可以无限次使用。
我们的目标是,通过最少的操作次数,让数组里所有数字的乘积 a_1 * a_2 * ... * a_n
能够被 k
整除。
简单来说,就是用最少的 "+1" 操作,让整个数组的乘积成为 k
的倍数。是不是很简单呀?喵~
题解方法
要让一堆数字的乘积能被 k
整除,其实就是要让这些数字分解质因数后,所有质因子的集合里,包含了 k
的所有质因子。
举个栗子,如果 k = 4
,因为 4 = 2 * 2
,所以我们需要所有数字的质因子中至少有两个 2
。
因为 k
的值非常小(只有 2, 3, 4, 5),我们可以分开讨论,找到最简单的策略!
策略一:改造一个数就搞定!
最直接的想法就是,我们只动一个数字 a_i
,让它自己变成 k
的倍数。这样一来,整个数组的乘积自然也就能被 k
整除了,对吧?
那么,要把一个数 a_i
变成 k
的倍数,需要多少次操作呢? 其实就是把它变成大于等于 a_i
的、最小的那个 k
的倍数。 比如 a_i = 7
, k = 5
。7
不是 5
的倍数,下一个 5
的倍数是 10
。我们需要 10 - 7 = 3
次操作。 如果 a_i = 10
, k = 5
,它已经是 5
的倍数了,那就不需要操作,0
次!
这个操作次数可以用一个很漂亮的公式来算:(k - a_i % k) % k
。
a_i % k
是a_i
除以k
的余数。k - (a_i % k)
就是还需要加多少才能凑成k
。- 最后再
% k
是为了处理a_i
本身就是k
的倍数的情况。这时a_i % k
是0
,k - 0 = k
,但我们其实需要0
次操作,所以k % k = 0
正好解决了这个问题。
我们可以遍历数组里所有的 a_i
,对每个 a_i
都计算这个操作次数,然后取一个最小值。这个就是我们的一个备选答案 ans
。
对于 k
是质数(2, 3, 5)的情况,这个策略就是最优的了。因为质数不能再分解,我们必须得凑出一个 k
的倍数来。
策略二:特殊情况 k = 4
当 k = 4
的时候,事情就变得有趣起来了,喵~ 4 = 2 * 2
,所以我们需要乘积的质因子中至少有两个 2
。 这可以通过两种方式实现:
- 和上面一样:让某一个
a_i
变成4
的倍数。这个成本我们已经算过了。 - 合作的力量:让两个不同的数
a_i
和a_j
都变成2
的倍数(也就是偶数)。一个偶数提供一个质因子2
,两个偶数就提供了两个质因子2
,它们的乘积自然就是4
的倍数啦!
现在我们来计算一下第二种方式的成本: 我们需要凑够两个偶数。我们先数一数数组里本来就有多少个偶数,记作 evens_count
。
- 如果
evens_count >= 2
,太棒了!我们已经有两个或更多偶数了,什么都不用做,操作次数为0
。 - 如果
evens_count == 1
,我们还差一个偶数。找一个奇数,给它+1
,它就变成偶数了。成本是1
。 - 如果
evens_count == 0
,我们需要两个偶数。那就找两个奇数,都给它们+1
,成本是1 + 1 = 2
。
所以,对于 k = 4
的情况,最终的答案是 min(策略一的最小操作数, 策略二的最小操作数)。
总结一下
- 初始化一个
min_ops
为k
(一个比较大的安全值)。 - 遍历数组中每个数
val
:- 计算让
val
变成k
的倍数所需的操作数(k - val % k) % k
,用它来更新min_ops
。 - 如果
k=4
,顺便数一下数组里有多少个偶数evens_count
。
- 计算让
- 遍历结束后:
- 如果
k=4
,再计算一下凑够两个偶数的操作数max(0, 2 - evens_count)
,然后用它和当前的min_ops
比较,取更小的一个。 - 如果
k
是 2, 3, 5,直接输出min_ops
就好啦。
- 如果
就是这样,喵~ 是不是很清晰了呢?
题解
这是可以直接运行的 C++ 代码哦,我已经加上了可爱的注释,方便主人理解每一行都在做什么~
#include <iostream>
#include <vector>
#include <numeric>
#include <algorithm>
// 解决单个测试用例的函数喵~
void solve() {
int n;
int k;
std::cin >> n >> k;
// 喵~ 先假设最坏情况,操作次数最多是k-1次,比如把1变成k。
// 为了方便,直接设成k也没问题。
int min_ops = k;
// 如果k=4,我们需要数一数有多少个偶数,喵呜~
int evens_count = 0;
for (int i = 0; i < n; ++i) {
int val;
std::cin >> val;
// 策略一:让一个数变成k的倍数。
// (k - val % k) % k 这个表达式很巧妙,能直接算出需要加上的值。
// 如果val已经是k的倍数,val % k = 0, (k-0)%k = 0,正好是0次操作!
min_ops = std::min(min_ops, (k - val % k) % k);
// 如果 k 是 4,我们就要特别关注偶数啦。
if (val % 2 == 0) {
evens_count++;
}
}
// 策略二:只在 k=4 的时候需要考虑。
// 目标是让数组里有两个偶数。
if (k == 4) {
// 我们需要 2 个偶数,现在已经有 evens_count 个了。
// 如果 evens_count = 0, 还需要 2 个, 操作 2 次。
// 如果 evens_count = 1, 还需要 1 个, 操作 1 次。
// 如果 evens_count >= 2, 还需要 0 个, 操作 0 次。
// 这个逻辑可以用 max(0, 2 - evens_count) 来概括,真聪明~
int ops_from_evens = std::max(0, 2 - evens_count);
// 最终答案是两种策略里更优的那个!
min_ops = std::min(min_ops, ops_from_evens);
}
std::cout << min_ops << "\n";
}
int main() {
// 快速输入输出,让程序跑得像小猫一样快~
std::ios_base::sync_with_stdio(false);
std::cin.tie(NULL);
int t;
std::cin >> t;
while (t--) {
solve();
}
return 0;
}
知识点介绍
这道题虽然简单,但里面藏着一些很有用的数学小知识哦!
模运算 (Modular Arithmetic)
%
运算符在编程里就是取余数的意思。a % k
得到a
除以k
的余数。- 这个知识点在本题的核心应用是计算将一个数
val
变为k
的倍数所需的最少操作数。公式(k - val % k) % k
是一个非常简洁和优雅的实现。它避免了使用if-else
来判断val
是否已经是k
的倍数,让代码更紧凑。
整除性与质因数分解 (Divisibility and Prime Factorization)
- 核心思想:一个数
P
能被k
整除,当且仅当k
的所有质因子(包括重复的)都在P
的质因子集合中。 - 应用到本题:我们的
P
是a_1 * a_2 * ... * a_n
。它的质因子集合是所有a_i
的质因子集合的并集。 k=2, 3, 5
(质数):k
只有一个质因子,就是它本身。所以我们只需要让乘积中出现一个因子k
就行。最简单的方法就是让某个a_i
成为k
的倍数。k=4
(合数):4
的质因子是2, 2
。我们需要两个因子2
。- 可以来源于同一个
a_i
(a_i
是4
的倍数,比如4, 8, 12...
)。 - 也可以来源于两个不同的
a_i
和a_j
(它俩都是2
的倍数,也就是偶数)。 - 这就是为什么
k=4
时需要一个特殊的分支逻辑来比较两种策略的优劣。
- 可以来源于同一个
- 核心思想:一个数
理解了这些,下次遇到类似的题目,主人也一定能像我一样,一眼就看穿它的小把戏啦!喵~ 如果还有不懂的地方,随时可以再来问我哦!