1 条题解

  • 0
    @ 2026-5-6 15:42:34

    G1. 排列问题(简单版) – 详细题解

    问题重述

    给定一个 11nn 的排列 pp,求有多少对 (i,j)(i, j)1i<jn1 \le i < j \le n)满足 pipjp_i \cdot p_j 能被 iji \cdot j 整除。

    关键转化

    条件 ijpipji \cdot j \mid p_i \cdot p_j 等价于

    $$\frac{p_i}{i} \cdot \frac{p_j}{j} \text{ 是整数?不准确,因为公因子会约分。正确方式:引入最大公因数。 $$

    gi=gcd(i,pi)g_i = \gcd(i, p_i),令

    xi=pigi,yi=igi.x_i = \frac{p_i}{g_i}, \quad y_i = \frac{i}{g_i}.

    注意 xix_iyiy_i 互质(因为除去了最大公因数)。那么条件 ijpipji j \mid p_i p_j 可改写为:

    $$\frac{p_i p_j}{i j} = \frac{x_i g_i \cdot x_j g_j}{y_i g_i \cdot y_j g_j} = \frac{x_i x_j}{y_i y_j}. $$

    由于 xix_iyiy_i 互质,xjx_jyjy_j 互质,故分数 xixjyiyj\frac{x_i x_j}{y_i y_j} 为整数当且仅当分母的每个素因子都能被分子约去。因为 xix_iyiy_i 无公共因子,所以 yiy_i 必须整除 xjx_j,且 yjy_j 必须整除 xix_i。即

    yixjyjxi.y_i \mid x_j \quad \text{且} \quad y_j \mid x_i.

    因此,每对 (i,j)(i,j) 满足条件等价于:

    $$\frac{p_i}{\gcd(i,p_i)} \text{ 是 } \frac{j}{\gcd(j,p_j)} \text{ 的倍数,且反过来也成立?实际上双向条件可简化为:} y_i \mid x_j \text{ 且 } y_j \mid x_i. $$

    进一步简化

    由于 xi=pigix_i = \frac{p_i}{g_i}yi=igiy_i = \frac{i}{g_i} 互质,且 pinp_i \le nini \le n,所以所有 xix_iyiy_i 都是不超过 nn 的整数。条件 yixjy_i \mid x_j 表明 xjx_jyiy_i 的倍数。我们想要统计所有无序对 (i,j)(i, j) 满足双向整除关系。注意到若 yixjy_i \mid x_jyjxiy_j \mid x_i,则两个方向必须同时成立。我们可以将每个下标 ii 映射到一对互质的数 (xi,yi)(x_i, y_i),然后计数满足 yixjy_i \mid x_jyjxiy_j \mid x_i 的对数。

    一种经典处理是枚举所有可能的 (x,y)(x,y) 对,但 x,yx,y 的取值范围都是 O(n)O(n),直接枚举 O(n2)O(n^2) 不可行。代码中使用了根号分治技巧:

    • 对于每个 ii,我们得到 ai=xia_i = x_ibi=yib_i = y_i
    • 条件变为:biajb_i \mid a_jbjaib_j \mid a_i

    算法思路

    我们采用离线处理,从大到小遍历 ii,并维护已经处理过的 jj(即 j>ij > i 或反过来)。在代码中,从 nn 向下扫描,每次查询当前 ii 与所有已处理(即更大下标)的 jj 的配对情况。

    对于当前 ii,我们需要统计已处理的下标 jj 中满足 biajb_i \mid a_jbjaib_j \mid a_i 的个数。由于我们按 ii 递减处理,已处理的 jj 都大于当前 ii,而 (i,j)(i,j) 无序对只需统计一次。

    利用 bjaib_j \mid a_i 枚举因子

    条件 bjaib_j \mid a_i 意味着 bjb_jaia_i 的因子。同时 biajb_i \mid a_j 意味着 aja_jbib_i 的倍数。因此,对于固定的 ii,我们可以枚举 aia_i 的所有因子 dd,然后考虑所有满足 bj=db_j = d 的那些 jj,且还要满足 biajb_i \mid a_j。此时 aja_j 必须是 bib_i 的倍数。所以我们需要在已处理的 jj 中,对每个因子 dd,统计有多少个 jj 满足 bj=db_j = daja_jbib_i 的倍数。

    根号分治

    直接维护所有 (bj,aj)(b_j, a_j) 对,并对于每个 ii 枚举因子 ddO(ai)O(\sqrt{a_i}) 个),然后查询 bib_i 的倍数,这需要能够快速回答:对于给定的 bbdd,有多少个 jj 使得 bj=db_j = daja_jbb 的倍数。我们可以预先将 bjb_j 按值分类,对于出现次数较少的 bb(小类),直接存储其所有 aja_j 的列表,查询时遍历该列表检查 bib_i 是否整除 aja_j。对于出现次数很多的 bb(大类),则维护一个数组 cnt[d][m]cnt[d][m] 表示对于该 bb 值,aa 值为 mm 的个数,并对倍数累加预处理?代码中采用的方法:

    • 设定阈值 B=nB = \sqrt{n}(大约 350)。
    • 对于出现次数 B\le Bbb 值,将其视为“小 bb”,我们直接存储所有对应的 aa 值列表。查询时,对于每个因子 dd,若 dd 是小 bb,则遍历其列表,检查 biajb_i \mid a_j
    • 对于出现次数 >B> Bbb 值,将其视为“大 bb”,我们为每个大 bb 维护一个长度为 nn 的数组 ff,其中 f[x]f[x] 表示当前已处理的 jj 中,满足 bj=bb_j = baj=xa_j = x 的数量。然后,为了快速回答 bib_i 的倍数,我们可以通过对倍数累加:对每个大 bb,我们维护一个额外的数组 g[y]=biajf[aj]g[y] = \sum_{b_i \mid a_j} f[a_j],但这里 bib_i 是变量,所以每次查询需要枚举 bib_i 的倍数 tt,将 f[t]f[t] 求和。由于 bib_i 也是 O(n)O(n),但 bib_i 的倍数个数约 n/bin / b_i,当 bib_i 较小时可能很多。但代码中采用了另一种策略:在查询时,遍历 bib_i 的倍数 mulmul,然后对于每个大 bb,直接累加其数组中的 f[mul]f[mul]。由于大 bb 个数很少(最多 n/Bn/B),而 bib_i 的倍数个数最多 nn,但总复杂度可控。

    实际上,代码实现如下:

    • 预处理所有数的因子列表 divs
    • 对每个下标 ii,计算 g=gcd(i,pi)g = \gcd(i, p_i)a[i]=pi/ga[i] = p_i / gb[i]=i/gb[i] = i / g
    • 统计每个 bb 出现的次数 cnt_b
    • 标记大 bb(出现次数 > B),并为其分配一个 ID,同时准备一个二维数组 big[id] 用于计数 aa 值(大小 n+1n+1)。
    • 对于小 bb,用一个 vector 列表存储每个小 bb 对应的所有 aa 值。
    • i=ni = n 向下遍历:
      • 查询:枚举 a[i]a[i] 的所有因子 dd(这些 dd 就是候选的 bjb_j)。对于每个 dd
        • 如果 dd 是大 bb,则获取其 ID,然后枚举 b[i]b[i] 的所有倍数 mulmul,将 big[id][mul] 累加到答案。
        • 如果 dd 是小 bb,则遍历该 dd 对应的 aa 值列表 vecvec,统计有多少个 aja_j 能被 b[i]b[i] 整除。
      • 注意:这里遍历 b[i]b[i] 的倍数时,要确保 j>ij > i,但因为我们是从大到小插入,所以目前 bigbigsmallsmall 中只包含已经处理过的 jj(即下标大于当前 ii 的),所以正确。
      • 最后将当前 (a[i],b[i])(a[i], b[i]) 插入到对应的数据结构中:如果 b[i]b[i] 是大 bb,则 big[id][a[i]]++;否则将 a[i]a[i] 加入到 small[b[i]] 列表中。

    最终输出答案即可。

    复杂度分析

    • 预处理因子:O(nlogn)O(n \log n)
    • 计算 ai,bia_i,b_iO(n)O(n)
    • 根号分治:每个 ii 枚举因子个数 O(ai)O(\sqrt{a_i}),对于每个因子 dd,若 dd 是大 bb,则枚举 bib_i 的倍数,总数约为 n/1+n/2+...=O(nlogn)n/1 + n/2 + ... = O(n \log n) 但只发生在 dd 是大 bb 时,由于大 bb 个数少,实际总复杂度 O(nn)O(n \sqrt{n}) 可接受(n105n \le 10^5)。若 dd 是小 bb,则遍历其列表,每个元素在被遍历时只属于它作为 dd 的情况,且每个元素最多被遍历 n\sqrt{n} 次?其实每个小 bb 的列表长度不超过 BB,而每个 ii 的因子个数 O(n)O(\sqrt{n}),所以总复杂度 O(nn)O(n \sqrt{n})

    由于 n105\sum n \le 10^5,该算法足够快。

    总结

    本题的关键在于将整除条件转化为互质因子关系,进而导出 biajb_i \mid a_jbjaib_j \mid a_i。然后通过根号分治平衡小频率和大频率的查询,利用因子枚举和倍数枚举高效统计满足条件的数对。代码实现中细节处理较多,但整体思路清晰,时间复杂度可接受。

    • 1

    信息

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