diff --git a/docs/math/code/sqrt-decomposition/sqrt-decomposition_1.cpp b/docs/math/code/sqrt-decomposition/sqrt-decomposition_1.cpp new file mode 100644 index 000000000000..24607633f815 --- /dev/null +++ b/docs/math/code/sqrt-decomposition/sqrt-decomposition_1.cpp @@ -0,0 +1,18 @@ +#include + +long long H(int n) { + long long res = 0; // 储存结果 + int l = 1, r; // 块左端点与右端点 + while (l <= n) { + r = n / (n / l); // 计算当前块的右端点 + res += (r - l + 1) * 1LL * + (n / l); // 累加这一块的贡献到结果中。乘上 1LL 防止溢出 + l = r + 1; // 左端点移到下一块 + } + return res; +} + +main() { + int t, n; + for (scanf("%d", &t); t--; scanf("%d", &n), printf("%lld\n", H(n))); +} diff --git a/docs/math/code/sqrt-decomposition/sqrt-decomposition_2.cpp b/docs/math/code/sqrt-decomposition/sqrt-decomposition_2.cpp new file mode 100644 index 000000000000..e4739e20930f --- /dev/null +++ b/docs/math/code/sqrt-decomposition/sqrt-decomposition_2.cpp @@ -0,0 +1,22 @@ +#include + +#include +#define N 100009 +int n, a[N], maxn; +long long ans[N]; + +main() { + scanf("%d", &n); + for (int i = 1; i <= n; scanf("%d", a + i), maxn = std::max(maxn, a[i]), ++i); + for (int i = 0; i < n; ++i) + for (int l = 1, r;; l = r + 1) { + r = std::min(l < a[i] ? (a[i] - 1) / ((a[i] - 1) / l) : N, + l < a[i + 1] ? (a[i + 1] - 1) / ((a[i + 1] - 1) / l) + : N); // 二维数论分块 + if (r == N) break; + int x = (a[i + 1] - 1) / l - std::max(a[i] - 1, 0) / l; + if (x > 0) ans[l] += x, ans[r + 1] -= x; // 累加贡献 + } + ++ans[0]; // ⌈a/l⌉=(a-1)/l+1的式子当a=0时不成立,需要修正 + for (int i = 1; i <= maxn; printf("%lld ", ans[i] += ans[i - 1]), ++i); +} diff --git a/docs/math/examples/sqrt-decomposition/sqrt-decomposition_1.ans b/docs/math/examples/sqrt-decomposition/sqrt-decomposition_1.ans new file mode 100644 index 000000000000..f0ae1dfbc136 --- /dev/null +++ b/docs/math/examples/sqrt-decomposition/sqrt-decomposition_1.ans @@ -0,0 +1,2 @@ +10 +27 diff --git a/docs/math/examples/sqrt-decomposition/sqrt-decomposition_1.in b/docs/math/examples/sqrt-decomposition/sqrt-decomposition_1.in new file mode 100644 index 000000000000..6cbe7d86175c --- /dev/null +++ b/docs/math/examples/sqrt-decomposition/sqrt-decomposition_1.in @@ -0,0 +1,3 @@ +2 +5 +10 diff --git a/docs/math/examples/sqrt-decomposition/sqrt-decomposition_2.ans b/docs/math/examples/sqrt-decomposition/sqrt-decomposition_2.ans new file mode 100644 index 000000000000..9c7f9ed9eeb3 --- /dev/null +++ b/docs/math/examples/sqrt-decomposition/sqrt-decomposition_2.ans @@ -0,0 +1 @@ +10 6 4 3 2 2 1 diff --git a/docs/math/examples/sqrt-decomposition/sqrt-decomposition_2.in b/docs/math/examples/sqrt-decomposition/sqrt-decomposition_2.in new file mode 100644 index 000000000000..6c08133c99a7 --- /dev/null +++ b/docs/math/examples/sqrt-decomposition/sqrt-decomposition_2.in @@ -0,0 +1,2 @@ +3 +5 2 7 diff --git a/docs/math/number-theory/sqrt-decomposition.md b/docs/math/number-theory/sqrt-decomposition.md index d77e3bf1999f..1de3f11cd9ad 100644 --- a/docs/math/number-theory/sqrt-decomposition.md +++ b/docs/math/number-theory/sqrt-decomposition.md @@ -62,7 +62,7 @@ $$ \left\lfloor\dfrac ni\right\rfloor=\left\lfloor\dfrac nj\right\rfloor $$ -成立的最大的满足 $i\leq j\leq n$ 的 $j$ 的值为 $\left\lfloor\dfrac n{\lfloor\frac ni\rfloor}\right\rfloor$。即值 $\left\lfloor\dfrac ni\right\rfloor$ 所在的块的右端点为 $\left\lfloor\dfrac n{\lfloor\frac ni\rfloor}\right\rfloor$。 +成立且满足 $i\leq j\leq n$ 的 $j$ 值最大为 $\left\lfloor\dfrac n{\lfloor\frac ni\rfloor}\right\rfloor$,即值 $\left\lfloor\dfrac ni\right\rfloor$ 所在块的右端点为 $\left\lfloor\dfrac n{\lfloor\frac ni\rfloor}\right\rfloor$。 ??? note "证明" 令 $k=\left\lfloor\dfrac ni\right\rfloor$,可以知道 $k\leq\dfrac ni$。 @@ -109,17 +109,7 @@ $$ ??? note "参考实现" ```cpp - long long H(int n) { - long long res = 0; // 储存结果 - int l = 1, r; // 块左端点与右端点 - while (l <= n) { - r = n / (n / l); // 计算当前块的右端点 - res += (r - l + 1) * 1LL * - (n / l); // 累加这一块的贡献到结果中。乘上 1LL 防止溢出 - l = r + 1; // 左端点移到下一块 - } - return res; - } + --8<-- "docs/math/code/sqrt-decomposition/sqrt-decomposition_1.cpp" ``` ???+ note "N 维数论分块" @@ -129,6 +119,41 @@ $$ 一般我们用的较多的是二维形式,此时可将代码中 `r = n / (n / i)` 替换成 `r = min(n / (n / i), m / (m / i))`。 +## 向上取整的数论分块 + +向上取整与前文所述的向下取整十分类似,但略有区别: + +对于常数 $n$,使得式子 + +$$ +\left\lceil\dfrac ni\right\rceil=\left\lceil\dfrac nj\right\rceil +$$ + +成立且满足 $i\leq j\leq n$ 的 $j$ 值最大为 $\left\lfloor\dfrac{n-1}{\lfloor\frac{n-1}i\rfloor}\right\rfloor$,即值 $\left\lceil\dfrac ni\right\rceil$ 所在块的右端点为 $\left\lfloor\dfrac{n-1}{\lfloor\frac{n-1}i\rfloor}\right\rfloor$。 + +???+ warning "注意" + 当 $i=n$ 时,上式会出现分母为 $0$ 的错误,需要特殊处理。 + +??? note "证明" + $\left\lceil\dfrac ni\right\rceil=\left\lfloor\dfrac{n-1}i\right\rfloor+1$,可以发现 $n$ 的上取整分块与 $n-1$ 的下取整分块是一样的。 + +???+ note " 例题:[CF1954E Chain Reaction](https://codeforces.com/contest/1954/problem/E)" + 题意:有一排 $n$ 个怪兽,每个怪兽初始血量为 $a_i$,一次攻击会使一段连续的存活的怪兽血量减 $k$,血量不大于 $0$ 视作死亡,对于所有 $k$ 求出击杀所有怪兽所需攻击次数,$n,a_i\leq 10^5$。 + + ??? note "思路" + 下面是一种使用二维数论分块的解法: + + 使用[积木大赛](https://www.luogu.com.cn/problem/P1969)的技巧,令 $a_0=0$,对于某个 $k$,答案就是 $\sum\limits_{i=1}^n\max\left(0,\left\lceil\dfrac{a_i}{k}\right\rceil-\left\lceil\dfrac{a_{i-1}}{k}\right\rceil\right)$。 + + 对于相邻的两个怪兽,使用二维数论分块,分段求出它们对一段 $k$ 的答案的贡献,然后差分累加即可。 + + 复杂度 $O(\sum\sqrt{a_i})$。也存在其他解法。 + + ??? note "实现" + ```cpp + --8<-- "docs/math/code/sqrt-decomposition/sqrt-decomposition_2.cpp" + ``` + ## 习题 1. [CQOI2007 余数求和](https://www.luogu.com.cn/problem/P2261)(需要一点转化和特判)