Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(math/number-theory/sqrt-decomposition.md): 新增向上取整的数论分块相关内容 #5563

Merged
merged 5 commits into from
May 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions docs/math/code/sqrt-decomposition/sqrt-decomposition_1.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#include <iostream>

long long H(int n) {
long long res = 0; // 储存结果
int l = 1, r; // 块左端点与右端点
while (l <= n) {
r = n / (n / l); // 计算当前块的右端点
// 累加这一块的贡献到结果中。乘上 1LL 防止溢出
res += 1LL * (r - l + 1) * (n / l);
l = r + 1; // 左端点移到下一块
}
return res;
}

int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int t, n;
std::cin >> t;
while (t--) {
std::cin >> n;
std::cout << H(n) << '\n';
}
return 0;
}
29 changes: 29 additions & 0 deletions docs/math/code/sqrt-decomposition/sqrt-decomposition_2.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#include <algorithm>
#include <iostream>

constexpr int N = 100009;
int n, a[N], maxn;
long long ans[N];

int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cin >> n;
for (int i = 1; i <= n; ++i) {
std::cin >> a[i];
maxn = std::max(maxn, a[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; ++i)
std::cout << (ans[i] += ans[i - 1]) << " \n"[i == maxn];
return 0;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
10
27
3 changes: 3 additions & 0 deletions docs/math/examples/sqrt-decomposition/sqrt-decomposition_1.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
2
5
10
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
10 6 4 3 2 2 1
2 changes: 2 additions & 0 deletions docs/math/examples/sqrt-decomposition/sqrt-decomposition_2.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
3
5 2 7
49 changes: 37 additions & 12 deletions docs/math/number-theory/sqrt-decomposition.md
Original file line number Diff line number Diff line change
Expand Up @@ -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$。
Expand Down Expand Up @@ -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 维数论分块"
Expand All @@ -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)(需要一点转化和特判)
Expand Down
Loading