【整除分块】【P2261】余数求和

· · 题解

【整除分块】【P2261】余数求和

马上 EC Final 了,来复健一下算法竞赛技能。

Description

给定 n,k,求

\sum_{i = 1}^n k \bmod i ## Analysis 考虑处理取模。一个经典套路是 $k \bmod i = k - i\left\lfloor\frac k i \right\rfloor$(回忆 exgcd 的证明也是一样的式子)。于是: $$\begin{aligned}\sum_{i = 1}^n k \bmod i &= \sum_{i = 1}^n (k - i \left\lfloor\frac k i \right\rfloor) \\ &= \sum_{i = 1}^n k - \sum_{i = 1}^n i\left\lfloor\frac k i \right\rfloor\end{aligned}$$ 减号左边就是 $nk$,可以 $O(1)$ 算,右边长了一副可以整除分块的脸。 > 整除分块: > 对已知部分和(前缀和)的函数 $f(x)$,计算 $\sum_{i = 1}^n f(i) \left\lfloor\frac n i \right\rfloor$ 的方法: > > 易证 $\left\lfloor\frac n i \right\rfloor$ 只有 $O(\sqrt n)$ 种取值,且 $i$ 增大时,该式取值不减。可以证明,对于一个确定的 $x$,满足 $\left\lfloor\frac n j \right\rfloor = x$ 的最大的 $j$ 为 $\left\lfloor\frac n x \right\rfloor$。考虑把 $\left\lfloor\frac n i \right\rfloor$ 值相同的 $i$ 看作一块,那么第一块的左端点为 $i = 1$;假如当前块的左端点是 $i = l$,则右端点为 $i = r = \left\lfloor\frac {n}{\left\lfloor\frac n i \right\rfloor} \right\rfloor$,下一块的左端点为 $r + 1$。 > > 枚举这 $O(\sqrt n)$ 个块。对于一个块 $[l, r]$,对所求式子的贡献就是 $\left\lfloor\frac n l \right\rfloor \times \sum_{i = l}^r f(i)$。如果可以 $O(T)$ 求函数 $f(x)$ 的部分和,就可以用 $O(T \sqrt n)$ 的复杂度算出所求。 > > 更细致的介绍见 [OI wiki](https://oi-wiki.org/math/number-theory/sqrt-decomposition/)。 考虑式子 $\sum_{i = 1}^n i\left\lfloor\frac k i \right\rfloor$,对应到整除分块有 $f(x) = x$,但是和整除分块长得不一样的地方在于,求和上限是 $n$ 而不是 $k$。这样是无法保证复杂度的。 考虑把原式拆成 $i \leq k$ 和 $i > k$ 两部分。易见,当 $i > k$ 时,$k \bmod i = k$。而 $i \leq k$ 情况的求和上界至多是 $k$。 于是 $$\begin{aligned}\sum_{i = 1}^n k \bmod i &= \max(0, n - k) \times k + \sum_{i = 1}^{\min(n, k)} (k - i \left\lfloor\frac k i \right\rfloor) \\ &= \max(0, n - k) \times k + \min(n, k) \times k - \sum_{i = 1}^{\min (n,k)}i\left\lfloor\frac k i \right\rfloor\end{aligned}$$ 等号右侧前两项都可以 $O(1)$ 计算,第三项相当于对 $\sum_{i = 1}^k i \left\lfloor\frac k i \right\rfloor$ 做整除分块,但是把块右端点最大值限制为 $\min(n, k) \leq k$,复杂度显然是 $O(\sqrt n)$。 ## Code ```cpp #include <iostream> int main() { int n, k; std::cin >> n >> k; auto sum = [](int l, int r) -> long long { return (l + r) * (r - l + 1ll) >> 1; }; long long ans = 0; if (k < n) { ans = 1ll * (n - k) * k; n = k; } ans += 1ll * n * k; for (int l = 1; l <= n; ++l) { int r = std::min(n, k / (k / l)); ans -= sum(l, r) * (k / l); l = r; } std::cout << ans << std::endl; } ```