1 条题解

  • 0
    @ 2025-11-23 19:57:58

    题目分析

    给定 NN 个正整数 a1,a2,,aNa_1, a_2, \dots, a_N,可以进行合并相邻数或拆分一个数的操作。
    对于每个查询 qq,求让所有数都等于 qq 的最小操作次数,若不可能则输出 1-1


    核心思路

    1. 问题转化

    设总和 S=aiS = \sum a_i,最终要有 kk 个数,每个都是 qq,则:

    • 必要条件:qq 必须整除 SS,即 k=S/qk = S / q
    • 操作次数 = 合并次数 + 拆分次数

    2. 关键观察

    操作次数公式

    • 最终数组长度 k=S/qk = S / q
    • 原数组中能直接作为 qq 的"完整段"数量 = mp[q]mp[q](预处理)
    • 操作次数 = (Nk)+(kmp[q])=N+k2mp[q](N - k) + (k - mp[q]) = N + k - 2 \cdot mp[q]

    其中:

    • NkN - k:合并操作次数(从 NN 个数减少到 kk 个数)
    • kmp[q]k - mp[q]:拆分操作次数(需要补充的 qq 的数量)

    算法实现详解

    1. 质因数分解与预处理

    Miller-Rabin 素数测试 + Pollard's Rho

    bool isPrime(u64 n);        // Miller-Rabin 确定性测试
    u64 rho(u64 n);             // Pollard's Rho 分解
    vector<pair<u64, int>> factorize(u64 n);  // 返回质因数分解
    

    用于分解总和 SS 的所有质因数。

    生成所有因数

    vector<u64> all_divisors_from_pf(const vector<pair<u64, int>> &pf);
    

    通过质因数分解生成 SS 的所有因数。

    2. 核心预处理

    前缀和与 GCD 统计

    for (int i = 1; i <= n; i++)
        cin >> a[i], a[i] += a[i - 1];  // 计算前缀和
    
    for (int i = 1; i <= n; i++)
        mp[__gcd(a[i], a[n])]++;  // 统计每个GCD值的出现次数
    

    这里的关键:

    • a[i]a[i] 是前缀和,a[n]=Sa[n] = S
    • gcd(a[i],S)\gcd(a[i], S) 表示从开头到 ii 的区间和与 SS 的最大公约数
    • 这个 GCD 值能整除 SS,是 SS 的因数

    因数关系传播

    for (auto it : pf) {
        ll p = it.first;
        for (auto x : d)
            if (a[n] % ((u128)x * p) == 0)
                mp[x] += mp[x * p];
    }
    

    这里用类似高维前缀和的方法:

    • 如果 xpx \cdot pSS 的因数,那么所有能被 xpx \cdot p 整除的前缀,也能被 xx 整除
    • 因此 mp[x]mp[x] 应该包含 mp[xp]mp[x \cdot p] 的计数

    3. 查询处理

    while (m--) {
        ll x;
        cin >> x;
        if (a[n] % x)  // x 不能整除总和
            cout << "-1\n";
        else {
            int tot = n - mp[x];  // 需要修改的位置数
            cout << tot + a[n] / x - (n - tot) << "\n";
        }
    }
    

    操作次数计算

    • tot=nmp[x]tot = n - mp[x]:不能直接作为 xx 的段数
    • a[n]/xa[n] / x:最终段数 kk
    • 操作次数 = tot+k(ntot)=k+2totntot + k - (n - tot) = k + 2 \cdot tot - n

    复杂度分析

    • 质因数分解O(S1/4)O(S^{1/4}) 使用 Pollard's Rho
    • 生成所有因数O(d(S))O(d(S)),其中 d(S)d(S) 是因数个数
    • 预处理O(nlogS+d(S)ω(S))O(n \log S + d(S) \cdot \omega(S)),其中 ω(S)\omega(S) 是不同质因数个数
    • 查询O(1)O(1) 每次

    由于 S1018S \leq 10^{18}d(S)d(S) 最多约 10510^5 量级,可接受。


    算法标签

    主要算法

    • 数论
    • 质因数分解(Miller-Rabin + Pollard's Rho)
    • 前缀和
    • GCD 性质

    相关技术

    • 高维前缀和思想
    • 大数处理
    • 查询优化

    代码亮点

    1. 高效质因数分解:使用确定性 Miller-Rabin 和 Pollard's Rho
    2. GCD 统计技巧:通过 gcd(前缀和,S)\gcd(\text{前缀和}, S) 识别完整段
    3. 因数关系传播:利用质因数关系高效计算 mpmp 数组
    4. 大数安全计算:使用 __uint128_t 避免溢出

    总结

    这道题通过数论和前缀和的巧妙结合,将操作次数计算转化为因数统计问题。核心在于识别原数组中能直接作为目标值 qq 的"完整段",这通过 GCD 性质和质因数分解高效实现。算法充分利用了数论性质,实现了 O(1)O(1) 查询的优秀复杂度。

    • 1

    信息

    ID
    5540
    时间
    1000ms
    内存
    256MiB
    难度
    10
    标签
    递交数
    1
    已通过
    1
    上传者