1 条题解
-
0
题目分析
给定 个正整数 ,可以进行合并相邻数或拆分一个数的操作。
对于每个查询 ,求让所有数都等于 的最小操作次数,若不可能则输出 。
核心思路
1. 问题转化
设总和 ,最终要有 个数,每个都是 ,则:
- 必要条件: 必须整除 ,即
- 操作次数 = 合并次数 + 拆分次数
2. 关键观察
操作次数公式:
- 最终数组长度
- 原数组中能直接作为 的"完整段"数量 = (预处理)
- 操作次数 =
其中:
- :合并操作次数(从 个数减少到 个数)
- :拆分操作次数(需要补充的 的数量)
算法实现详解
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); // 返回质因数分解用于分解总和 的所有质因数。
生成所有因数
vector<u64> all_divisors_from_pf(const vector<pair<u64, int>> &pf);通过质因数分解生成 的所有因数。
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值的出现次数这里的关键:
- 是前缀和,
- 表示从开头到 的区间和与 的最大公约数
- 这个 GCD 值能整除 ,是 的因数
因数关系传播
for (auto it : pf) { ll p = it.first; for (auto x : d) if (a[n] % ((u128)x * p) == 0) mp[x] += mp[x * 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"; } }操作次数计算:
- :不能直接作为 的段数
- :最终段数
- 操作次数 =
复杂度分析
- 质因数分解: 使用 Pollard's Rho
- 生成所有因数:,其中 是因数个数
- 预处理:,其中 是不同质因数个数
- 查询: 每次
由于 , 最多约 量级,可接受。
算法标签
主要算法:
- 数论
- 质因数分解(Miller-Rabin + Pollard's Rho)
- 前缀和
- GCD 性质
相关技术:
- 高维前缀和思想
- 大数处理
- 查询优化
代码亮点
- 高效质因数分解:使用确定性 Miller-Rabin 和 Pollard's Rho
- GCD 统计技巧:通过 识别完整段
- 因数关系传播:利用质因数关系高效计算 数组
- 大数安全计算:使用
__uint128_t避免溢出
总结
这道题通过数论和前缀和的巧妙结合,将操作次数计算转化为因数统计问题。核心在于识别原数组中能直接作为目标值 的"完整段",这通过 GCD 性质和质因数分解高效实现。算法充分利用了数论性质,实现了 查询的优秀复杂度。
- 1
信息
- ID
- 5540
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者