1 条题解
-
0
「三重求和」题解
问题分析
给定序列 ,对于每个询问 ,计算:
$$S = \sum_{i=0}^{d-1} \sum_{j=0}^{d-1} \sum_{k=0}^{d-1} a_{p_1+d\cdot i+j} \cdot a_{p_2 + d\cdot j + k} $$其中 ,需要保证下标在 范围内。
表达式分析
1. 下标分析
设:
其中:
- 各自独立地从 到
- 依赖于 和
- 依赖于 和
2. 重写求和式
$$S = \sum_{j=0}^{d-1} \left( \sum_{i=0}^{d-1} a_{p_1+d\cdot i+j} \right) \cdot \left( \sum_{k=0}^{d-1} a_{p_2 + d\cdot j + k} \right) $$令:
- ,其中
- ,其中
则:
3. 几何解释
是从位置 开始,步长为 的 个元素之和。 是从位置 开始,连续的 个元素之和。
具体来说:
- :在序列 中,取位置 共 个元素求和
- :在序列 中,取位置 $p_2+d\cdot j, p_2+d\cdot j+1, p_2+d\cdot j+2, \dots, p_2+d\cdot j+(d-1)$ 共 个元素求和
算法思路
暴力方法(不可行)
直接按照公式计算:
- 对每个询问,计算 和 各需要 时间(因为每个 需要求 个元素的和)
- 总复杂度 ,最坏 ,不可行
优化方向
我们需要预处理一些信息,使得能够快速计算 和 。
优化 的计算
对于固定的 和 , 是 的函数。这可以看作在模 意义下的分组求和。
定义序列 (重新编号,从0开始),则:
$$A_j = \sum_{i=0}^{d-1} b_{j + d\cdot i} = \sum_{i: i \equiv j \ (\text{mod } d)} b_i $$即 是 序列中所有模 余 的位置的和。
优化 的计算
这是连续的 个元素的和,可以使用前缀和优化:
设 ,则:
$$B_j = pre[p_2 + d\cdot j + d - 1] - pre[p_2 + d\cdot j - 1] $$前提是下标在范围内。
预处理策略
对 的预处理
对于每个可能的模数 ,我们需要快速查询:从位置 开始,所有模 余 的位置的和。
设 表示从位置 开始,长度为 的范围内,所有模 余 的位置的元素和。
但由于 最多可达 ,无法为所有 预处理。
根号分治
使用根号分治策略:
- 对于小的 ():预处理所有可能的信息
- 对于大的 ():直接暴力计算,因为此时循环次数 较小
设阈值 (因为 )。
小 的情况()
预处理数组 表示:前 个位置中,所有模 余 的元素之和。
那么:
$$A_j = sum[d][j][p_1 + (d-1)d + j] - sum[d][j][p_1 - 1] $$但注意:我们只需要从 开始,每隔 取一个元素,共取 个。
更精确地说: 是从 开始,每隔 取一个,取 次。
可以预处理 表示从位置 开始,每隔 取一个,取 个的和。但这样空间太大。
更好的方法:对于每个小 ,我们预处理一个前缀和数组 ,表示前 个位置中,所有模 余 的元素之和,余 的元素之和,...,余 的元素之和。
但 最多 ,每个 需要 个位置,每个位置存储 个余数的和,总空间 ,太大。
实际上,我们可以只存储每个余数的前缀和: 表示前 个位置中模 余 的元素之和。
空间:$O(\sum_{d=1}^B d \cdot n) = O(B^2 \cdot n) = O(n^{1.5} \cdot n) = O(n^{2.5})$,还是太大。
需要进一步优化。
改进的小 预处理
注意到 需要的是从特定位置开始,每隔 取一个的和,而不是所有模 余 的和。
我们可以预处理另一种结构:对于每个 ,将序列分成 个等差数列,然后对每个等差数列预处理前缀和。
具体来说:
- 对于模 余 的位置:
- 对这些位置建立前缀和数组 ,表示前 个这样的元素的和
那么 就是从 开始(这是第 个这样的元素),取 个连续元素的和。
空间:每个 有 个等差数列,每个数列长度约 ,总空间 $O(\sum_{d=1}^B d \cdot (n/d)) = O(B \cdot n) = O(n^{1.5})$,可接受。
大 的情况()
此时 较大,但 的上限是多少?由于 ,所以 ,即 。
等等,检查一下: 最大是 ,所以确实 。
因此 ,这与我们的阈值 相同!
这意味着所有询问的 都满足 !
重要发现:由于下标限制 ,所以 。
因此所有询问的 都是"小"的,我们可以直接使用小 的预处理方法。
最终算法
预处理
设
对于每个 到 :
- 将序列分成 个等差数列:对于 到 ,位置为
- 对每个等差数列建立前缀和数组 ,其中 表示该等差数列前 个元素的和
具体实现:对于每个 ,我们有 个数组,每个数组长度约为 。
查询计算
对于询问 :
1. 计算 ()
对于每个 , 是等差数列 中从某个位置开始的连续 个元素的和。
从哪个位置开始?我们需要找到 在这个等差数列中是第几个元素。
设等差数列为: 元素 在这个数列中的索引为:?不对, 可能不是等差数列的起点。
实际上,等差数列的第一个元素是某个 满足 ,且 。更简单的方法:
等差数列中的第 个元素是 。
我们需要找到最小的 使得 ,即 ,所以 ?
实际上更简单:我们要求的是从 开始(包含),每隔 取一个,取 个。
设 ,那么取的序列是:
这些元素在等差数列 中的索引:
第一个元素 在等差数列中的索引:$t_1 = \frac{start - j}{d} = \frac{p_1+j - j}{d} = \frac{p_1}{d}$
最后一个元素 的索引:$t_2 = \frac{p_1+j+(d-1)d - j}{d} = \frac{p_1}{d} + (d-1)$
所以 ,使用前缀和计算。
2. 计算 ()
这是连续 个元素的和,使用普通的前缀和:
$$B_j = pre\_global[p_2 + d\cdot j + d - 1] - pre\_global[p_2 + d\cdot j - 1] $$其中 是全局前缀和。
3. 计算答案
需要模 ,可以使用
unsigned int自动取模。复杂度分析
预处理
- 对于每个 ,需要处理 个等差数列
- 每个等差数列平均长度
- 总元素数:$\sum_{d=1}^B d \cdot (n/d) = B \cdot n = O(n^{1.5})$
- 预处理复杂度:
查询
- 对于每个询问,需要计算 个 和 个
- 每个 需要 时间(通过预处理的等差数列前缀和)
- 每个 需要 时间(通过全局前缀和)
- 计算点积需要 时间
- 总查询复杂度:
由于 ,总复杂度 ,对于 ,最坏 ,可接受。
实现细节
1. 数据结构
pre_global[i]:全局前缀和- 对于小 的预处理:使用二维向量或数组存储
pre[d][r][k]- 第一维:(1到)
- 第二维:余数 (0到)
- 第三维:前缀和索引
2. 内存优化
由于 , 的最大值是447。 对于每个 ,需要存储 个数组。 总元素数大约 $447 \times n = 447 \times 2\times 10^5 \approx 9\times 10^7$ 个整数。 每个整数4字节,约360MB,可能太大。
需要进一步优化内存:
优化方案:
- 不存储所有 的预处理信息,只存储 较小的部分
- 或者,对于每个 ,我们不需要存储所有余数的前缀和,只需要在查询时临时计算
实际上,计算 也可以通过另一种方式: 就是从 开始,每隔 取一个,取 个。我们可以直接计算这 个元素的和,每个元素用全局前缀和快速获取。
3. 简化实现
由于 ,我们可以直接暴力计算 :
对于每个 :
这需要 时间计算一个 ,总共 时间计算所有 。
而 ,所以 。
对于 ,使用全局前缀和 计算。
总查询复杂度:?不对,是 。
最坏情况:,每个 ,,总 ,太大。
4. 折中方案
使用阈值 :
- 当 时,使用预处理方法
- 当 时,直接暴力计算 (此时 较小, 也较小)
设 (对于 ),但 最大只有447,所以所有 都小于 。
总结
最终算法:
- 预处理全局前缀和
- 对于 到 ,预处理每个等差数列的前缀和
- 对于每个询问:
- 用 时间计算每个 (全局前缀和)
- 用 时间计算每个 (等差数列前缀和)
- 计算点积
复杂度:
- 预处理:
- 查询:
对于 ,,,均可接受。
关键优化是发现 的约束,从而可以使用根号分治策略。
- 1
信息
- ID
- 5905
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者