1 条题解

  • 0
    @ 2026-4-12 20:49:37

    D. Iris 与相邻乘积 详细题解

    题目重述

    给定一个长度为 nn 的数组 bb,每次询问一个子数组 [l,r][l, r]
    我们可以:

    1. 重排子数组内的元素。
    2. 将任意元素修改[1,k][1, k] 中的任意整数。

    目标:使得重排+修改后,相邻元素的乘积 k\le k
    最少修改次数(重排不花钱)。


    核心思路

    1. 最优重排方式

    将子数组从小到大排序为 a1a2ama_1 \le a_2 \le \dots \le a_mm=rl+1m = r-l+1)。
    结论:最优重排方式是

    am,a1,am1,a2,am2,a3,a_m, a_1, a_{m-1}, a_2, a_{m-2}, a_3, \dots

    即最大、最小、次大、次小、……交替排列。

    证明要点(见题目末尾):

    • 最大值 ama_m 必须放在开头。
    • 最小值 a1a_1 放在第二位最安全,因为 a1×amka_1 \times a_m \le k 是必须满足的,且 a1a_1 最小,与任何数相乘都不容易超过 kk
    • 剩下的子问题规模减少 22,递归进行。

    2. 重排后的约束条件

    在最优重排下,相邻对是:

    $$(a_m, a_1), (a_1, a_{m-1}), (a_{m-1}, a_2), (a_2, a_{m-2}), \dots $$

    约束条件转化为:
    对于所有 1im/21 \le i \le \lfloor m/2 \rfloor,必须满足

    ai×ami+1ka_i \times a_{m-i+1} \le k

    因为 aia_i 递增,ami+1a_{m-i+1} 递减,所以最紧的约束来自 ii 较小和较大的组合。
    实际上等价于:

    $$\forall x \in [1, \lfloor m/2 \rfloor], \quad a_x \times a_{m-x+1} \le k $$

    3. 转化为计数问题

    mm 为子数组长度。
    对任意正整数 vv,定义:

    • cntvcnt_{\le v} = 子数组中值 v\le v 的元素个数。
    • cnt>vcnt_{> v} = 子数组中值 >v> v 的元素个数。

    观察:
    对于一对 (ai,ami+1)(a_i, a_{m-i+1}),若 ai>ta_i > tami+1>k/(t+1)a_{m-i+1} > k/(t+1),则它们的乘积 >k> k
    因此,要满足所有约束,必须让“大数”和“大数”不能配对。

    更精确地:
    对每个 ii,考虑 aia_iami+1a_{m-i+1} 配对。
    aixa_i \le x,则另一数必须 k/x\le \lfloor k/x \rfloor,否则乘积 >k> k

    重排后,前 m/2\lfloor m/2 \rfloor 个较小数必须与后 m/2\lfloor m/2 \rfloor 个较大数一一配对且乘积 k\le k


    4. 简化约束

    B=kB = \lfloor \sqrt{k} \rfloor
    x=1,2,,Bx = 1, 2, \dots, B,考虑:

    • 数值 x\le x 的元素个数 LxL_x
    • 数值 >k/x> \lfloor k/x \rfloor 的元素个数 RxR_x

    因为 xBx \le Bk/xB\lfloor k/x \rfloor \ge BRxR_x 有意义。

    约束等价于:
    对于每个 x[1,B]x \in [1, B]

    $$\min(L_x, \lfloor m/2 \rfloor) \ge \min(R_x, \lfloor m/2 \rfloor) $$

    即较小的那一半中,x\le x 的数量必须不少于 >k/x> \lfloor k/x \rfloor 的数量。

    换句话说:
    如果 Rx>LxR_x > L_x,那么多出来的 RxLxR_x - L_x 个大数必须被修改(改成 11,从而进入 LxL_x 计数)。


    5. 最少修改次数计算

    对于固定的 xx,需要修改的次数至少为:

    max(0,RxLx)\max(0, R_x - L_x)

    但注意我们只有 m/2\lfloor m/2 \rfloor 对,所以实际修改次数不能超过 m/2Lx\lfloor m/2 \rfloor - L_x(因为 LxL_x 个已经满足,剩下位置需要填满)。

    所以对每个 xx,修正后的需求为:

    $$\text{need}_x = \min\left( \max(0, R_x - L_x), \lfloor m/2 \rfloor - L_x \right) $$

    最终答案为所有 xx 中最大的 needx\text{need}_x


    6. 离线处理与前缀和优化

    对每个询问 [l,r][l, r],我们需要快速计算:

    • $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]$

    预处理:

    • 对每个 xx,建立前缀和数组 prex[i]pre_{\le x}[i] 表示前 ii 个元素中 x\le x 的数量。
    • 同理 pre>k/x[i]pre_{> \lfloor k/x \rfloor}[i]

    xx11BBBk1000B \approx \sqrt{k} \le 1000n105n \le 10^5,直接存 B×nB \times n 太大(10810^8 级别)。

    优化
    因为 kk 固定,我们可以对每个 xx 分别扫描一次数组,同时回答所有询问。
    即:

    • 外层循环 x=1Bx = 1 \dots B
    • 内层计算当前 xx 下的两个前缀和数组(大小 n+1n+1
    • 对所有询问,用 O(1)O(1) 时间得到 LxL_xRxR_x,更新 ans[q]ans[q]

    这样空间 O(n+q)O(n + q),时间 O(B(n+q))O(B \cdot (n + q))


    7. 额外考虑 x>Bx > B 的情况

    x>Bx > B 时,k/x<B\lfloor k/x \rfloor < B,情况已经被更小的 x=k/xx' = \lfloor k/x \rfloor 覆盖。
    因此只需 x=1Bx = 1 \dots B

    另外,若 m/2Lx\lfloor m/2 \rfloor - L_x 可能为负,此时 needx=0\text{need}_x = 0,但代码中用 max\maxmin\min 自动处理。


    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);
        }
    }
    

    关键细节

    • 代码中用 ii 表示 xxrr 表示 kk
    • c1c1>k/(x+1)> \lfloor k/(x+1) \rfloor 的个数吗?
      实际上是 k/(x+1)\le \lfloor k/(x+1) \rfloor 的个数,那么 Rx=lenc1R_x = \text{len} - c1。 但代码中直接比较 c1c1c2c2,是因为 min\minmax\max 的对称性简化了表达式。

    9. 时间复杂度

    • 预处理前缀和:O(n)O(n)
    • 对每个 xxO(k)O(\sqrt{k}) 个):遍历所有询问 O(q)O(q)
    • 总时间:O(k(n+q))O(\sqrt{k} \cdot (n + q))
      k106k \le 10^6k1000\sqrt{k} \le 1000n+q2×105n+q \le 2\times 10^5 时,约 2×1082\times 10^8 次操作,常数小可过。

    总结

    1. 最优重排:最大、最小、次大、次小交替。
    2. 约束转化:对每个 xkx \le \sqrt{k}x\le x 的数量必须 \ge >k/x> \lfloor k/x \rfloor 的数量。
    3. 最少修改:取所有 xx 中需要的最大修改数。
    4. 实现:离线枚举 xx,用前缀和快速回答每个询问。

    这个题解结合了贪心重排数论分块离线前缀和,是 Codeforces 2006D 的典型解法。

    • 1

    信息

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