[BJOI2018] 链上二次求和 题解
原问题没法直接维护。先推式子,看看能得到什么。
设 \(f(l, r)\) 表示给定 \(l, r\) 时的答案。那么根据定义,有:
记 \(s(m) = \sum_{i = 1}^{m} a_i\),则我们可以去掉第三个求和符号:
记 \(ss(m) = \sum_{i = 1}^{m} s(i)\),继续推导,去掉第二个求和符号:
记 \(sss(m) = \sum_{i = 1}^{m} ss(i)\),继续推导,去掉最后的求和符号:
(为了应对边界条件,定义当 \(m \le 0\) 时,\(ss(m) = sss(m) = 0\),这是自然的。)
以上,我们就去掉了所有的求和符号,这是进一步优化算法的基础。实际上,有了这个式子之后,我们就可以做到 \(O(1)\) 查询。但修改时,我们必须重新求出 \(s, ss, sss\) 数组,这样做的时间复杂度为 \(O(n)\)。
想想怎么优化。看到题目中的区间操作,很自然地想到用线段树维护。那么,维护什么以及如何维护呢?
首先明确我们的需求:区间修改,快速计算 \(ss(m), sss(m)\)。从这里以后有两条路:
-
预处理出原序列 \(a\) 的 \(ss\) 数组。修改时,考察 \(a\) 的区间加对 \(ss\) 产生的影响,用线段树维护 \(ss\) 的区间和。维护 \(ss\) 的好处在于:\(sss\) 是 \(ss\) 的前缀和,因此要求 \(sss\) 时,只要在线段树上查询 \(ss\) 的区间和即可。
-
维护 \(a\) 和一些辅助数组,把 \(ss\) 和 \(sss\) 都写成用 \(a\) 表示的形式。
注意 \(ss\) 和 \(sss\) 是不能作为区间信息维护的。实际上,带“前缀”的信息都不能作为区间信息维护。以更简单的一阶前缀和 \(s\) 为例,考虑这样的问题:区间加,求任意位置的前缀和。在线段树中,对于一个区间 \([l, r]\),我们不能维护区间 \([l, r]\) 的前缀和 \(s\),因为这不能合并:\([l, mid]\) 的前缀和与 \([mid + 1, r]\) 的前缀和相加,不能得到 \([l, r]\) 的前缀和。我们只能维护 \([l, r]\) 的区间和,而这个信息是可以合并的。求某个位置 \(p\) 的前缀和,看作 \([1, p]\) 的区间和即可。
如果选择这种方式,要继续推式子。
下面选用第二种方法。继续推导:
这里出现了新的求和式:\(\sum_{i = 1}^{m}ia_i\),我们记为 \(si(m)\),则
这样我们就把 \(ss\) 用 \(s\) 和 \(si\) 表示了出来。显然,\(s(m)\) 是容易用 \(a\) 表示的:对 \(a\) 求区间和即可。而 \(si\) 也是可以维护的——它不是某种“前缀”和的形式,因此可以在线段树上合并信息。至于如何维护,等会再说。
下面推导 \(sss\) 的式子:
\(\sum_{i = 1}^{m}is(i)\) 和 \(\sum_{i = 1}^{m} si(i)\) 都不能直接维护,继续推导:
(推到第二个等号的要点在于计算每个 \(a_i\) 前的系数:这里,每个 \(a_i\) 从 \(i\) 开始计算,系数为 \(i\),然后为 \(i + 1\),\(i + 2\),一直到 \(m\),因此总系数为 \(\sum_{j = i}^{m} j\)。)
记 \(sii(m) = \sum_{i = 1}^{m}i^2a_i\),和 \(si\) 类似,这也是可以合并的信息,因此可以用线段树维护。那么
接着来推导 \(sss\) 的式子中剩下的另一个和式:\(\sum_{i = 1}^{m} si(i)\):
(这里推导的要点在于把 \(ia_i\) 看作一个整体,然后推导方式和 \(ss\) 相同,只是把求和对象从 \(a_i\) 换成了 \(ia_i\)。)
至此,我们终于可以写出 \(sss\) 的式子:
从 \(ss\) 和 \(sss\) 的式子可以看出,我们需要维护的量有 \(s, si\) 和 \(sii\)。最后要考虑的是怎么维护区间加对这些量的影响。
考虑一个区间 \([l, r]\),设它的三个量为 \(s, si\) 和 \(sii\)(定义同上文)。设区间加 \(d\) 后,新的量为 \(s', si'\) 和 \(sii'\)。那么
对于 \(\sum_{i = l}^{r}1, \sum_{i = l}^{r}i\) 和 \(\sum_{i = l}^{r}i^2\),可以用公式求,也可以预处理后查表。这显然是好维护的。
回顾一下我们要做什么:
- 在线段树上维护 \(s, si\) 和 \(sii\)。
- 对于每次询问,根据公式求出答案。公式中要用到 \(ss\) 和 \(sss\),这些也要代入公式求,但总归能化到我们维护的三个量。
每次询问时,我们只会在线段树上查询常数次,所以总时间复杂度为 \(O(n + m \log n)\)。
综上所述,我们就解决了这个问题。