1 条题解
-
0
G1. 排列问题(简单版) – 详细题解
问题重述
给定一个 到 的排列 ,求有多少对 ()满足 能被 整除。
关键转化
条件 等价于
$$\frac{p_i}{i} \cdot \frac{p_j}{j} \text{ 是整数?不准确,因为公因子会约分。正确方式:引入最大公因数。 $$设 ,令
注意 与 互质(因为除去了最大公因数)。那么条件 可改写为:
$$\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}. $$由于 与 互质, 与 互质,故分数 为整数当且仅当分母的每个素因子都能被分子约去。因为 与 无公共因子,所以 必须整除 ,且 必须整除 。即
因此,每对 满足条件等价于:
$$\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. $$进一步简化
由于 与 互质,且 ,,所以所有 和 都是不超过 的整数。条件 表明 是 的倍数。我们想要统计所有无序对 满足双向整除关系。注意到若 且 ,则两个方向必须同时成立。我们可以将每个下标 映射到一对互质的数 ,然后计数满足 且 的对数。
一种经典处理是枚举所有可能的 对,但 的取值范围都是 ,直接枚举 不可行。代码中使用了根号分治技巧:
- 对于每个 ,我们得到 ,。
- 条件变为: 且 。
算法思路
我们采用离线处理,从大到小遍历 ,并维护已经处理过的 (即 或反过来)。在代码中,从 向下扫描,每次查询当前 与所有已处理(即更大下标)的 的配对情况。
对于当前 ,我们需要统计已处理的下标 中满足 且 的个数。由于我们按 递减处理,已处理的 都大于当前 ,而 无序对只需统计一次。
利用 枚举因子
条件 意味着 是 的因子。同时 意味着 是 的倍数。因此,对于固定的 ,我们可以枚举 的所有因子 ,然后考虑所有满足 的那些 ,且还要满足 。此时 必须是 的倍数。所以我们需要在已处理的 中,对每个因子 ,统计有多少个 满足 且 是 的倍数。
根号分治
直接维护所有 对,并对于每个 枚举因子 ( 个),然后查询 的倍数,这需要能够快速回答:对于给定的 和 ,有多少个 使得 且 是 的倍数。我们可以预先将 按值分类,对于出现次数较少的 (小类),直接存储其所有 的列表,查询时遍历该列表检查 是否整除 。对于出现次数很多的 (大类),则维护一个数组 表示对于该 值, 值为 的个数,并对倍数累加预处理?代码中采用的方法:
- 设定阈值 (大约 350)。
- 对于出现次数 的 值,将其视为“小 ”,我们直接存储所有对应的 值列表。查询时,对于每个因子 ,若 是小 ,则遍历其列表,检查 。
- 对于出现次数 的 值,将其视为“大 ”,我们为每个大 维护一个长度为 的数组 ,其中 表示当前已处理的 中,满足 且 的数量。然后,为了快速回答 的倍数,我们可以通过对倍数累加:对每个大 ,我们维护一个额外的数组 ,但这里 是变量,所以每次查询需要枚举 的倍数 ,将 求和。由于 也是 ,但 的倍数个数约 ,当 较小时可能很多。但代码中采用了另一种策略:在查询时,遍历 的倍数 ,然后对于每个大 ,直接累加其数组中的 。由于大 个数很少(最多 ),而 的倍数个数最多 ,但总复杂度可控。
实际上,代码实现如下:
- 预处理所有数的因子列表
divs。 - 对每个下标 ,计算 ,,。
- 统计每个 出现的次数
cnt_b。 - 标记大 (出现次数 > B),并为其分配一个 ID,同时准备一个二维数组
big[id]用于计数 值(大小 )。 - 对于小 ,用一个
vector列表存储每个小 对应的所有 值。 - 从 向下遍历:
- 查询:枚举 的所有因子 (这些 就是候选的 )。对于每个 :
- 如果 是大 ,则获取其 ID,然后枚举 的所有倍数 ,将
big[id][mul]累加到答案。 - 如果 是小 ,则遍历该 对应的 值列表 ,统计有多少个 能被 整除。
- 如果 是大 ,则获取其 ID,然后枚举 的所有倍数 ,将
- 注意:这里遍历 的倍数时,要确保 ,但因为我们是从大到小插入,所以目前 和 中只包含已经处理过的 (即下标大于当前 的),所以正确。
- 最后将当前 插入到对应的数据结构中:如果 是大 ,则
big[id][a[i]]++;否则将 加入到small[b[i]]列表中。
- 查询:枚举 的所有因子 (这些 就是候选的 )。对于每个 :
最终输出答案即可。
复杂度分析
- 预处理因子:。
- 计算 :。
- 根号分治:每个 枚举因子个数 ,对于每个因子 ,若 是大 ,则枚举 的倍数,总数约为 但只发生在 是大 时,由于大 个数少,实际总复杂度 可接受()。若 是小 ,则遍历其列表,每个元素在被遍历时只属于它作为 的情况,且每个元素最多被遍历 次?其实每个小 的列表长度不超过 ,而每个 的因子个数 ,所以总复杂度 。
由于 ,该算法足够快。
总结
本题的关键在于将整除条件转化为互质因子关系,进而导出 且 。然后通过根号分治平衡小频率和大频率的查询,利用因子枚举和倍数枚举高效统计满足条件的数对。代码实现中细节处理较多,但整体思路清晰,时间复杂度可接受。
- 1
信息
- ID
- 6991
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者