1 条题解
-
0
题目详解
核心观察
设整个数组的全局最大公约数为:
结论1: 最终所有元素都会变成 。
证明思路:
- 如果最终所有数都等于 ,则 必须是每个原始 的因数(因为操作只产生原数的因数),所以 。
- 另一方面,任何操作得到的新数都是两个数的最大公约数,它至少是 (因为 能整除所有数,也能整除它们的最大公约数)。
- 因此 。
分类讨论
情况1: 已经存在于数组中
若某个 ,则我们可以用它对其他元素执行操作:
对于每个 ,执行一次操作:。
操作次数 = 数组中不等于 的元素个数。
情况2: 不在数组中
我们需要先通过若干次操作,制造出一个等于 的元素,然后再用这个元素去改变其他元素。
设用 次操作得到 ,则总操作次数为:
注意:当 被制造出来时,它所在的原始位置可能已经等于 ,但原来的那个元素可能已经改变了,不过这不影响计数。我们的策略是:先 制造出一个 ,然后对每个非 元素做一次操作。
如何求最小
我们定义 表示:将一个元素变成 所需的最少操作次数。
初始状态:
- 对于每个原始元素 ,(不需要操作就已经是它本身)。
转移规则: 如果我们已经有办法在 步内得到某个数 ,那么再执行一次操作(选择一个任意的 作为“搭档”),就可以得到 :
因为我们可以先花 步让某个位置变成 ,然后让目标位置与 取 。
优化
直接按上述方程做,复杂度 可能会超时。
预处理
由于 ,可以预处理所有 的 ,存入二维数组 。预处理复杂度 ,其中 ,这是可行的。
从大到小更新
因为 ,所以我们从 到 枚举,这样能保证在更新 时, 已经是最优值。
对于每个 ,遍历所有原始元素 ,进行转移。
最终我们关心的是 ,因为当所有数都除以 后,全局 变成了 。
为什么除以 后再处理?
代码中首先将整个数组除以 ,即 。这样处理后,新数组的全局 为 。
这样做的好处:
- 处理时数值范围不变(仍然是 ),但目标变成了得到 。
- 原问题等价于:在新数组中,先花 步制造出一个 ,然后让所有大于 的元素都变成 。
最终答案的计算
- 先将所有 除以 。
- 用 DP 计算出 ,即制造一个 的最少步数。
- 如果数组中已经有 ,则 ,否则 。
- 总操作次数 = 。
注意:如果 ,意味着制造 的过程本身会占用一个元素,这个元素在完成制造后已经变成 ,所以它 不需要再被额外转换一次。因此公式成立。
代码对应关系
k = g[k][a[i]]; // 计算全局 gcd a[i] /= k; // 除以全局 gcd f[a[i]] = 0; // 初始状态 for(int x = m ; x >= 1 ; x --) for(int i = 1 ; i <= n ; i ++) { int y = a[i]; checkmin(f[g[x][y]], f[x] + 1); } ans = max(f[1] - 1, 0); // 制造1的步数(如果本来有1则为0) for(int i = 1 ; i <= n ; i ++) if(a[i] > 1) ans ++; // 把所有>1的变成1注意: 至少是 (如果没有初始 ),但制造 的过程可能已经改变了某个元素,所以 不能直接加,而是 才是“净制造步数”。因此用 。
复杂度分析
- 预处理 :,。
- 每个测试用例:,其中 是数组最大值。
- 总 之和不超过 ,所以最坏 ,可接受。
示例验证
样例1:
- ,除以 得 。
- 初始 。
- 从大到小更新,可得到 :
- ,
- ,
- 所以 ,制造 需 步。
- 大于 的元素有 个,总操作数 = ?但答案给的是 。
这里注意:实际上制造 的过程中,那个变出 的元素 原来也是大于 的,所以它被计入了“大于 的元素”中,但变成 后不需要再操作一次。因此答案公式应为:
$$\text{总操作数} = f[1] + (\text{大于 }1\text{ 的元素个数}) - 1 $$因为多算了一次。修正后:,正确。
而代码中
ans = max(f[1] - 1, 0)这一行实际上就是先做 ,然后后面再加非 元素个数,等价于 。
- 1
信息
- ID
- 7130
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 3
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者