1 条题解
-
0
题解:倍数计数与前缀和优化算法
问题分析
本题模拟了一个循环操作:对于每个跳跃步长 ,将所有下标为 倍数的位置加1。执行 次后,需要回答 个区间和查询。
朴素做法:直接模拟每次操作需要 ,最坏 ,不可行。需要优化。
关键观察
1. 问题本质
对于位置 (),它的值 等于满足 是 倍数的 的个数。
即:
其中 整除 表示 是 的倍数(注意 时,所有 都整除它)。
2. 转化为倍数计数
设 表示步长 出现的次数。那么:
其中 表示 整除 ( 是 的倍数)。
3. 高效计算
需要对于所有 计算其所有因子的 之和。可以使用倍数枚举法(类似埃氏筛)在 时间内完成。
算法框架
第一步:统计步长频率
for (int i = 1; i <= k; ++i) { int x; scanf("%d", &x); ++cnt[x]; // cnt[d]记录步长d出现的次数 }第二步:倍数枚举计算seq值
关键代码:
for (int i = n>>1; i; --i) for (int j = i*2; j < n; j += i) seq[j] += cnt[i];解释:
- 从 向下枚举到1
- 对于每个 ,枚举 的所有倍数
- 将 加到 上
这样,最终 就包含了所有满足 的 之和。
第三步:处理特殊情况
是所有 的倍数(因为 除以任何非零数都得0):
seq[0] = k; // 所有K次操作都会访问位置0第四步:前缀和优化查询
预处理前缀和:
for (int i = 1; i < n; ++i) prefix[i] = prefix[i-1] + seq[i];查询 区间和:
printf("%lld\n", prefix[R] - (L ? prefix[L-1] : 0));
复杂度分析
-
时间复杂度:
- 倍数枚举:
- 前缀和预处理:
- 查询: 每个,总
- 总复杂度:
-
空间复杂度: 存储 数组
对于 , 操作,完全可行。
正确性证明
1. 倍数枚举的正确性
对于任意 ,它的因子 必须满足 (除了 本身,但 且 ,所以 本身不可能是步长)。枚举从 向下到1,确保所有可能的因子都被考虑。
更严谨的证明:对于每个 ,。由于 且 时 只能是 本身,而 不在步长范围内,所以只需考虑 。
2. 边界处理
- :特殊处理,所有操作都访问位置0
- :通过倍数枚举计算所有因子的贡献
3. 前缀和优化
区间和查询转化为两个前缀和的差,是标准优化方法。
算法优化细节
为什么从 开始枚举?
因为任何 的因子 如果满足 且 ,那么 只能是 本身。而题目保证 ,所以当 时, 本身不可能是步长(除非 恰好等于某个 ,但这种情况已由 直接处理)。
实际上,代码中
j = i*2开始枚举,确保了 严格小于 。内存优化
直接使用
a[]数组既存储 ,又存储 ,最后存储前缀和,节省空间。
总结
本题是一道数论与算法优化的综合题目,主要考察:
- 问题转化能力:将循环操作转化为倍数计数问题
- 倍数枚举技巧:使用类似埃氏筛的方法高效计算因子和
- 前缀和优化:将区间查询优化为 时间
- 边界条件处理:正确处理 的特殊情况
算法的核心技巧:
- 利用 的性质
- 通过从大到小枚举因子,避免重复计算
- 使用前缀和快速回答区间查询
这种"倍数枚举+前缀和"的方法是解决此类"倍数操作+区间查询"问题的经典范式,时间复杂度从朴素的 优化到 。
- 1
信息
- ID
- 5835
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者