1 条题解
-
0
D. Iris 与相邻乘积 详细题解
题目重述
给定一个长度为 的数组 ,每次询问一个子数组 。
我们可以:- 重排子数组内的元素。
- 将任意元素修改为 中的任意整数。
目标:使得重排+修改后,相邻元素的乘积 。
求最少修改次数(重排不花钱)。
核心思路
1. 最优重排方式
将子数组从小到大排序为 ()。
结论:最优重排方式是即最大、最小、次大、次小、……交替排列。
证明要点(见题目末尾):
- 最大值 必须放在开头。
- 最小值 放在第二位最安全,因为 是必须满足的,且 最小,与任何数相乘都不容易超过 。
- 剩下的子问题规模减少 ,递归进行。
2. 重排后的约束条件
在最优重排下,相邻对是:
$$(a_m, a_1), (a_1, a_{m-1}), (a_{m-1}, a_2), (a_2, a_{m-2}), \dots $$约束条件转化为:
对于所有 ,必须满足因为 递增, 递减,所以最紧的约束来自 较小和较大的组合。
$$\forall x \in [1, \lfloor m/2 \rfloor], \quad a_x \times a_{m-x+1} \le k $$
实际上等价于:
3. 转化为计数问题
设 为子数组长度。
对任意正整数 ,定义:- = 子数组中值 的元素个数。
- = 子数组中值 的元素个数。
观察:
对于一对 ,若 且 ,则它们的乘积 。
因此,要满足所有约束,必须让“大数”和“大数”不能配对。更精确地:
对每个 ,考虑 与 配对。
若 ,则另一数必须 ,否则乘积 。重排后,前 个较小数必须与后 个较大数一一配对且乘积 。
4. 简化约束
令 。
对 ,考虑:- 数值 的元素个数 。
- 数值 的元素个数 。
因为 时 , 有意义。
约束等价于:
$$\min(L_x, \lfloor m/2 \rfloor) \ge \min(R_x, \lfloor m/2 \rfloor) $$
对于每个 ,即较小的那一半中, 的数量必须不少于 的数量。
换句话说:
如果 ,那么多出来的 个大数必须被修改(改成 ,从而进入 计数)。
5. 最少修改次数计算
对于固定的 ,需要修改的次数至少为:
但注意我们只有 对,所以实际修改次数不能超过 (因为 个已经满足,剩下位置需要填满)。
所以对每个 ,修正后的需求为:
$$\text{need}_x = \min\left( \max(0, R_x - L_x), \lfloor m/2 \rfloor - L_x \right) $$最终答案为所有 中最大的 。
6. 离线处理与前缀和优化
对每个询问 ,我们需要快速计算:
- $L_x = \text{count of } b_i \le x \text{ in } [l, r]$
- $R_x = \text{count of } b_i > \lfloor k/x \rfloor \text{ in } [l, r]$
预处理:
- 对每个 ,建立前缀和数组 表示前 个元素中 的数量。
- 同理 。
但 从 到 ,,,直接存 太大( 级别)。
优化:
因为 固定,我们可以对每个 分别扫描一次数组,同时回答所有询问。
即:- 外层循环
- 内层计算当前 下的两个前缀和数组(大小 )
- 对所有询问,用 时间得到 和 ,更新 。
这样空间 ,时间 。
7. 额外考虑 的情况
当 时,,情况已经被更小的 覆盖。
因此只需 。另外,若 可能为负,此时 ,但代码中用 和 自动处理。
8. 标程解析
int B = sqrt(r); // r = k // 第一步:处理 <= B 的元素数量不足的情况 REP(i,0,q) { int len = y-x+1; int small = sum[y+1]-sum[x]; // <=B 的个数 ans[i] = max(0, len/2 - small); } // 第二步:枚举 x = 1..B REP(x,1,B+1) { // 前缀和 cnt: <=x 的数量 // 前缀和 cnt2: <= k/(x+1) 的数量 // 注意代码中是 <= r/(i+1) REP(j,0,q) { int len = y-x+1; int c1 = cnt2[y+1]-cnt2[x]; // <= k/(x+1) int c2 = cnt[y+1]-cnt[x]; // <= x // 需要修改的数 = 超出部分与可用位置的最小值 int need = min(max(0, c1 - c2), len/2 - c2); ans[j] = max(ans[j], need); } }关键细节:
- 代码中用 表示 , 表示 。
- 是 的个数吗?
实际上是 的个数,那么 。 但代码中直接比较 和 ,是因为 和 的对称性简化了表达式。
9. 时间复杂度
- 预处理前缀和:
- 对每个 ( 个):遍历所有询问
- 总时间:
当 ,, 时,约 次操作,常数小可过。
总结
- 最优重排:最大、最小、次大、次小交替。
- 约束转化:对每个 , 的数量必须 的数量。
- 最少修改:取所有 中需要的最大修改数。
- 实现:离线枚举 ,用前缀和快速回答每个询问。
这个题解结合了贪心重排、数论分块、离线前缀和,是 Codeforces 2006D 的典型解法。
- 1
信息
- ID
- 6499
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 1
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者