1 条题解

  • 0
    @ 2025-8-24 22:10:22

    自动搬运

    查看原文

    来自洛谷,原作者为

    avatar mrsrz
    故障机器人

    搬运于2025-08-24 22:10:22,当前版本为作者最后更新于2020-02-04 20:33:38,作者可能在搬运后再次修改,您可在原文处查看最新版

    自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多

    以下是正文


    第十四分块。

    这里只讲如何从 [1,i][1,i] 递推到 [1,i+1][1,i+1],关于二次离线莫队的具体写法请参见第十四分块(前体)的题解。

    考虑用 bib_i 表示 ii 的因数的出现次数,cic_i 表示 ii 的倍数的出现次数。那么一个数 xx 当前的贡献就为 bx+cxb_x+c_x

    考虑新加入 xx 这个数,所有 xx 的因数 yy 对应的 cyc_y 就要加 11,所有 xx 的倍数 zz 对应的 bzb_z 也要加 11

    5×1055\times 10^5 内的数的因数个数不多,单次枚举因数部分的复杂度可以看做 O(n)O(\sqrt n)

    但是当 xx 比较小的时候,枚举 xx 的倍数就是 O(n)O(n) 的。

    考虑根号分治,对 x>nx>\sqrt n 的部分直接暴力枚举,单次复杂度 O(n)O(\sqrt n),对于 xnx\leq \sqrt n 的部分,使用另外的方法。

    对于 xnx\leq\sqrt n 的部分,我们考虑解决二次离线莫队中两个需要维护的东西的计算。

    • 计算 [1,i1][1,i-1]ii 的贡献,即计算 aia_ i 的倍数在 [1,i1][1,i-1] 中的出现次数。

      aja_jaia_ij<ij\lt i)的倍数,相当于 aia_iaja_j 的因数,所以我们对每个数统计它作为因数的出现次数即可。

    • 计算 [1,i][1,i][l,r][l,r] 的贡献。我们还是要解决询问单点的倍数/因数个数的问题。但这里总询问次数为 O(nn)O (n\sqrt n),所以我们必须 O(1)O(1) 查询。

      现在的问题在于,我们无法统计 xx 的因数中,小于等于 n\sqrt n 的因数的出现次数。对于大于等于 n\sqrt n 的因数,直接枚举其倍数并加上贡献的复杂度是正确的,但小于等于时是不正确的。

      我们反过来考虑。我们对每个因数 xx,如果知道了 l,rl,rxx 的倍数的出现次数,那么这个 xx 作为因数的贡献就得到了。我们对每个 n\leq \sqrt n 的因数 xx 都计算贡献即可。

      考虑如何快速计算区间 [l,r][l,r]xx 的倍数的出现次数。我们对每个 xx,记录 prex,ipre_{x,i} 表示前 ii 个数中,xx 的倍数的出现次数。然后在计算 [1,i][1,i][l,r][l,r] 的贡献的时候,我们记录 [1,i][1,i] 中每个数的出现次数,然后 prex,rprex,l1pre_{x,r}-pre_{x,l-1} 乘上 xx[1,i][1,i] 的出现次数,就是 [l,r][l,r] 中的数作为倍数,[1,i][1,i] 中的数作为因数(不超过 n\sqrt n) 时的贡献。

    这样各部分维护的复杂度都为 O(nn)O(n\sqrt n)O(mn)O(m\sqrt n)

    时间复杂度 O((n+m)n)O((n+m)\sqrt n)

    关于空间复杂度,直接对 O(n)O(\sqrt n) 个因数开前缀和数组存是 O(nn)O(n\sqrt n) 的。可以只用一个前缀和数组,对每个因数分别重新计算,可以做到 O(n)O(n)

    卡常数部分:

    1. 线性空间的做法会被卡 Cache,所以要写空间 O(nn)O(n\sqrt n) 的做法。由于空间限制,根号分治的阈值只能开到 100200100\sim 200
    2. 每次都 O(n)O(\sqrt n) 计算一个数的因数效率非常低,可以开 vector 存每个数的因数,一开始预处理每个数的因数,然后就可以直接遍历 vector。时空复杂度 O(nlogn)O(n\log n)
    3. 存储二次离线的询问时,STL 的 vector 较慢,需要手动分配连续内存。
    4. IO 优化。
    5. 指令集优化。

    提交记录


    如果你想卡空间,那么需要用到一些技巧。

    线性空间(除去 O(nlogn)O(n\log n) 存储因数的部分)的做法,我们需要对每个 xx 都重新求前缀和数组,并对每个询问计算贡献。由于内存访问非常随机,卡不进 Cache,导致常数无法接受。

    所以我们的目标是卡进 Cache。

    一个非常 naive 的思路就是,对 n\sqrt n 这个阈值再分块,即我们开一个 nn4n\sqrt[4]n 的二维数组,存储连续的 n4\sqrt[4]n 个数作为因数的信息。这样我们在减小空间的基础上,又尽可能地卡进 Cache。

    理论空间复杂度是 O(nn4)O(n\sqrt[4]n)。实际上这个阈值没法取太大(因为大于阈值的部分只是一个纯粹的循环,常数非常小,而小于等于阈值的部分则非常吃常数),所以实际使用的空间也比较可观。

    提交记录

    • 1

    信息

    ID
    4375
    时间
    3000ms
    内存
    128MiB
    难度
    7
    标签
    递交数
    0
    已通过
    0
    上传者