这道来自GESP C++五级认证的题目,考察的是数论基础与贪心算法的结合应用。题目要求我们通过最少的操作次数,将给定序列中的所有元素变得相等。每次操作允许我们将序列中的某个元素乘以2或者除以2(如果能被2整除)。
在实际编程竞赛和算法学习中,这类题目非常典型——它既考察了对数学规律的洞察力,又考验了将数学观察转化为有效算法的能力。作为五级认证的题目,它确实有一定挑战性,但通过系统分析完全可以掌握。
提示:这类题目往往存在"关键观察点",一旦找到就能大幅简化问题。我们需要关注所有数字能否通过乘除2的操作达到某个共同值。
首先我们需要明确题目中操作的本质特性:
这意味着,所有操作都不会改变数字的"核心部分"——即去掉末尾所有0后的奇数部分。例如:
由此我们可以得出两个重要结论:
这直接引出了解题的第一个必要条件:序列中所有数字的核心必须相同,否则题目无解。
基于以上观察,我们的解题步骤应该是:
首先我们需要实现一个函数来提取数字的核心:
cpp复制int getCore(int x) {
while (x % 2 == 0) {
x /= 2;
}
return x;
}
这个函数通过不断除以2,直到得到一个奇数,即为该数字的核心。
接下来检查所有数字的核心是否一致:
cpp复制bool checkCores(const vector<int>& nums) {
if (nums.empty()) return true;
int core = getCore(nums[0]);
for (int num : nums) {
if (getCore(num) != core) {
return false;
}
}
return true;
}
这是最核心的部分。我们需要找到一个目标值,使得将所有数字调整到这个值所需的总操作步数最小。
cpp复制int minOperations(vector<int>& nums) {
// 检查核心一致性
if (!checkCores(nums)) return -1;
// 记录每个数字的右移次数(除以2的次数)
vector<int> shifts;
for (int& num : nums) {
int shift = 0;
while (num % 2 == 0) {
num /= 2;
shift++;
}
shifts.push_back(shift);
}
// 现在所有num都等于核心值
int core = nums[0];
// 我们需要找到一个目标shift,使得总步数最小
// 总步数 = Σ|shift_i - target|
// 这是一个典型的求中位数问题
sort(shifts.begin(), shifts.end());
int target = shifts[shifts.size() / 2]; // 中位数
int total = 0;
for (int s : shifts) {
total += abs(s - target);
}
return total;
}
cpp复制#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int getCore(int x) {
while (x % 2 == 0) {
x /= 2;
}
return x;
}
bool checkCores(const vector<int>& nums) {
if (nums.empty()) return true;
int core = getCore(nums[0]);
for (int num : nums) {
if (getCore(num) != core) {
return false;
}
}
return true;
}
int minOperations(vector<int>& nums) {
if (!checkCores(nums)) return -1;
vector<int> shifts;
for (int& num : nums) {
int shift = 0;
while (num % 2 == 0) {
num /= 2;
shift++;
}
shifts.push_back(shift);
}
sort(shifts.begin(), shifts.end());
int target = shifts[shifts.size() / 2];
int total = 0;
for (int s : shifts) {
total += abs(s - target);
}
return total;
}
int main() {
int n;
cin >> n;
vector<int> nums(n);
for (int i = 0; i < n; ++i) {
cin >> nums[i];
}
int res = minOperations(nums);
if (res == -1) {
cout << "-1" << endl;
} else {
cout << res << endl;
}
return 0;
}
输入1:
code复制3
4 8 2
输出1:
code复制2
解释:
输入2:
code复制3
4 8 6
输出2:
code复制-1
解释:
输入3:
code复制5
16 32 8 64 128
输出3:
code复制10
解释:
这个问题本质上是一个典型的"仓库选址"问题——在一条直线上找一点,使得该点到其他点的距离之和最小。数学上已经证明,中位数就是最优解。
这类算法在以下场景有实际应用:
在实际编程竞赛中,这类题目考察的不仅是编码能力,更重要的是数学建模和问题转化能力。建议多练习类似题目,培养对问题本质的洞察力。