1 条题解
-
0
「BalticOI 2025」姜饼 题解
问题分析
我们有一个数组 ,初始时所有数的最大公约数为 。如果 ,那么答案就是 。
如果 ,我们需要通过给某些元素增加一些值(每次增加 ),使得新的数组的最大公约数变为 。
关键观察:如果 ,那么所有 都能被 的某个质因子整除。要让 GCD 变为 ,我们必须破坏所有这些质因子的整除性。
数学洞察
设 是初始 GCD 的质因数分解。
对于每个质因子 ,当前所有 都能被 整除。要破坏 的整除性,我们需要至少修改一个元素 ,使得 不能被 整除(其中 是我们添加的姜饼数)。
重要性质:对于固定的 和质数 ,最小的 使得 是:
如果 ,那么 ;否则 。
问题转化
原问题转化为:给定质数集合 ( 的质因子),对于每个位置 ,修改它可以破坏所有满足 的质因子 (其中 是我们愿意在该位置添加的姜饼数)。
我们需要选择一组位置进行修改,使得:
- 所有质因子都被至少一个修改破坏
- 最大单次修改量最小化
- 总修改次数尽可能少
解决方案
算法思路
- 计算初始 GCD:
- 如果 :直接输出
- 质因数分解:分解 得到质因子集合
- 二分答案:对最终答案(最大单次修改量)进行二分
- 贪心验证:对于每个候选值 ,检查是否能用最多 的单次修改破坏所有质因子
详细算法
#include <bits/stdc++.h> using namespace std; const int MAX_A = 1e7 + 5; // 预处理最小质因子 vector<int> min_prime; void sieve(int n) { min_prime.resize(n + 1, 0); for (int i = 2; i <= n; i++) { if (min_prime[i] == 0) { min_prime[i] = i; for (long long j = 1LL * i * i; j <= n; j += i) { if (min_prime[j] == 0) min_prime[j] = i; } } } } // 获取一个数的所有质因子 vector<int> get_prime_factors(int x) { vector<int> factors; while (x > 1) { int p = min_prime[x]; factors.push_back(p); while (x % p == 0) x /= p; } return factors; } bool can_achieve(const vector<int>& a, const vector<int>& primes, int max_add) { int n = a.size(); int m = primes.size(); // 对于每个质数,记录是否已经被破坏 vector<bool> covered(m, false); int covered_count = 0; // 尝试每个位置,看它能用不超过max_add的代价破坏哪些质数 for (int i = 0; i < n && covered_count < m; i++) { vector<int> can_cover; for (int j = 0; j < m; j++) { if (covered[j]) continue; int p = primes[j]; int rem = a[i] % p; int needed = (rem == 0) ? p : p - rem; if (needed <= max_add) { can_cover.push_back(j); } } // 如果这个位置能破坏某些还未被破坏的质数,就使用它 if (!can_cover.empty()) { for (int j : can_cover) { if (!covered[j]) { covered[j] = true; covered_count++; } } } } return covered_count == m; } int solve(const vector<int>& a) { int n = a.size(); // 计算初始GCD int g = a[0]; for (int i = 1; i < n; i++) { g = gcd(g, a[i]); } if (g == 1) return 0; // 获取质因子 vector<int> primes = get_prime_factors(g); // 二分答案 int left = 1, right = *max_element(a.begin(), a.end()) + 1; int answer = right; while (left <= right) { int mid = (left + right) / 2; if (can_achieve(a, primes, mid)) { answer = mid; right = mid - 1; } else { left = mid + 1; } } return answer; } int main() { ios_base::sync_with_stdio(false); cin.tie(nullptr); // 预处理筛法 sieve(MAX_A - 1); int n; cin >> n; vector<int> a(n); for (int i = 0; i < n; i++) { cin >> a[i]; } cout << solve(a) << endl; return 0; }算法正确性证明
正确性:
- 如果初始 GCD 为 1,显然不需要任何操作
- 否则,我们必须破坏 的所有质因子
- 二分答案确保我们找到最小的最大单次修改量
- 贪心策略确保我们用最少的修改覆盖所有质因子
最优性:
- 对于每个质因子,我们至少需要修改一个位置来破坏它
- 我们的贪心策略每次选择能破坏最多未被覆盖质因子的位置
- 二分找到的就是最小的可行最大修改量
复杂度分析
- 预处理筛法:
- 质因数分解: 每次
- 二分答案: 次,其中 是 的最大值
- 每次验证:,其中 是 的质因子个数
由于 ,且 ,总复杂度是可接受的。
样例分析
对于样例输入:
3 90 84 140- 初始 GCD = gcd(90, 84, 140) = 2
- 质因子 = {2}
- 需要破坏质因子 2
- 各个位置破坏 2 所需的最小修改:
- 位置1 (90): 90 mod 2 = 0,需要 2
- 位置2 (84): 84 mod 2 = 0,需要 2
- 位置3 (140): 140 mod 2 = 0,需要 2
- 最小答案是 2(修改任意两个位置)
这个解法能够高效地解决该问题,并在大规模数据下保持良好性能。
- 1
信息
- ID
- 4042
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者