1 条题解

  • 0
    @ 2025-10-24 21:30:26

    「BalticOI 2025」姜饼 题解

    问题分析

    我们有一个数组 a1,a2,,ana_1, a_2, \ldots, a_n,初始时所有数的最大公约数为 g=gcd(a1,a2,,an)g = \gcd(a_1, a_2, \ldots, a_n)。如果 g=1g = 1,那么答案就是 00

    如果 g>1g > 1,我们需要通过给某些元素增加一些值(每次增加 11),使得新的数组的最大公约数变为 11

    关键观察:如果 g>1g > 1,那么所有 aia_i 都能被 gg 的某个质因子整除。要让 GCD 变为 11,我们必须破坏所有这些质因子的整除性。

    数学洞察

    g=p1k1p2k2pmkmg = p_1^{k_1} p_2^{k_2} \cdots p_m^{k_m} 是初始 GCD 的质因数分解。

    对于每个质因子 pjp_j,当前所有 aia_i 都能被 pjp_j 整除。要破坏 pjp_j 的整除性,我们需要至少修改一个元素 aia_i,使得 ai+xa_i + x 不能被 pjp_j 整除(其中 x1x \geq 1 是我们添加的姜饼数)。

    重要性质:对于固定的 aia_i 和质数 pp,最小的 x1x \geq 1 使得 p(ai+x)p \nmid (a_i + x) 是:

    x=p(aimodp)x = p - (a_i \bmod p)

    如果 aimodp=0a_i \bmod p = 0,那么 x=px = p;否则 x=p(aimodp)x = p - (a_i \bmod p)

    问题转化

    原问题转化为:给定质数集合 P={p1,p2,,pm}P = \{p_1, p_2, \ldots, p_m\}gg 的质因子),对于每个位置 ii,修改它可以破坏所有满足 p(aimodp)kp - (a_i \bmod p) \leq k 的质因子 pp(其中 kk 是我们愿意在该位置添加的姜饼数)。

    我们需要选择一组位置进行修改,使得:

    1. 所有质因子都被至少一个修改破坏
    2. 最大单次修改量最小化
    3. 总修改次数尽可能少

    解决方案

    算法思路

    1. 计算初始 GCDg=gcd(a1,a2,,an)g = \gcd(a_1, a_2, \ldots, a_n)
    2. 如果 g=1g = 1:直接输出 00
    3. 质因数分解:分解 gg 得到质因子集合 PP
    4. 二分答案:对最终答案(最大单次修改量)进行二分
    5. 贪心验证:对于每个候选值 kk,检查是否能用最多 kk 的单次修改破坏所有质因子

    详细算法

    #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,显然不需要任何操作
    • 否则,我们必须破坏 gg 的所有质因子
    • 二分答案确保我们找到最小的最大单次修改量
    • 贪心策略确保我们用最少的修改覆盖所有质因子

    最优性

    • 对于每个质因子,我们至少需要修改一个位置来破坏它
    • 我们的贪心策略每次选择能破坏最多未被覆盖质因子的位置
    • 二分找到的就是最小的可行最大修改量

    复杂度分析

    • 预处理筛法O(MAX_AloglogMAX_A)O(MAX\_A \log \log MAX\_A)
    • 质因数分解O(logg)O(\log g) 每次
    • 二分答案O(logA)O(\log A) 次,其中 AAaia_i 的最大值
    • 每次验证O(nm)O(n \cdot m),其中 mmgg 的质因子个数

    由于 mlog2glog2(107)24m \leq \log_2 g \leq \log_2(10^7) \approx 24,且 n106n \leq 10^6,总复杂度是可接受的。

    样例分析

    对于样例输入:

    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
    上传者