数论分块
libohan0905
·
·
个人记录
可结合题单:数论专题II食用
UVA11526 H(n)
题目描述
分析
把它写成数学的式子就是求:
首先,通过大眼观察法或者打表可以发现,其中有很多值都是重复的。
比如 $n=5$ 时,$\left\lfloor\dfrac{n}{i}\right\rfloor$ 分别为 ${5,2,1,1,1}$。
考虑每次计算直接一起算出重复的值。
观察值为 $\left\lfloor\dfrac{n}{i}\right\rfloor$ 的块的右边界,因为每次左边界都会有上一次右边界推出(也就是 $i$)所以不用管。
右边界也就是值为 $\left\lfloor\dfrac{n}{i}\right\rfloor$ 的 $i$ 最大的那一个,考虑什么时候可以取得到。
其实右边界是 $\left\lfloor\dfrac{n}{\left\lfloor\dfrac{n}{i}\right\rfloor}\right\rfloor$。可以感性理解。
想要详细证明可以看 [这个](https://www.luogu.com.cn/blog/486187/awawawa#:~:text=两种证明方式)。
现在每个块内值相等,都为 $\left\lfloor\dfrac{n}{i}\right\rfloor$,每块一共有 $j-i+1$ 个数,每次加上就好。
这样可以证明时间复杂度是 $\mathcal{O}(\sqrt n)$ 的,下面会证。
```cpp
typedef long long ll;
ll f(ll n){
ll ans=0;
for(ll i=1,j;i<=n;i=j+1){
j=n/(n/i);
ans=(ans+n/i%mod*(j-i+1))%mod;
}
return ans;
}
```
### 复杂度证明:
- 当 $x \in [1, \lfloor\sqrt n\rfloor]$ 这个区间,最多有 $\lfloor\sqrt n\rfloor$ 种取值。
- 当 $x \in (\lfloor\sqrt n\rfloor,n]$ 这个区间,$\lfloor\dfrac n x\rfloor$ 显然只能取到 $[1,\lfloor\sqrt n\rfloor)$ 这个区间,最多有 $\lfloor\sqrt n\rfloor$ 种取值。
则至多有 $2\lfloor\sqrt n\rfloor$ 种取值,复杂度为 $\mathcal{O}(\sqrt n)$。
---
### [P1403 [AHOI2005]约数研究](https://www.luogu.com.cn/problem/P1403)
即求 $1\sim n$ 约数个数的和。
注意到 $1\sim n$ 的因子个数,可以看成共含有 $2$ 因子的数的个数+含有 $3$ 因子的数的个数+......+含有 $n$ 因子的数的个数
但在 $1\sim n$ 中含有 $2$ 这个因子的数有 $\left\lfloor\dfrac{n}{2}\right\rfloor$ 个,$3$ 有 $\left\lfloor\dfrac{n}{3}\right\rfloor$ 个,以此类推。
问题就转化为了刚刚我们做出来的式子:$\sum\limits_{i=1}^n\left\lfloor\frac{n}{i}\right\rfloor$。
用同样的数论分块去做。
~~因为这题是橙题,所以直接暴力算式子也是可以的。~~
### 相关习题
- [P2261 [CQOI2007]余数求和](https://www.luogu.com.cn/problem/P2261)
注意式子转化。
- [P2424 约数和](https://www.luogu.com.cn/problem/P2424)
注意区别,这里求的是约数和,那么每一块再套一个求和公式呗。
- [P3935 Calculating](https://www.luogu.com.cn/problem/P3935)
你得知道这是约数个数定理。
---
### [P2260 [清华集训2012]模积和](https://www.luogu.com.cn/problem/P2260)
求 $\sum_{i=1}^{n} \sum_{j=1}^{m} (n \bmod i) \times (m \bmod j), i \neq j$ mod 19940417 的值
- 对于 $100\%$ 的数据,保证 $1 \leq n,m \leq 10^9$。
什么东西,先看那个 $i\not=j$ 不顺眼,先拆了。
$$\sum_{i=1}^{n}(n \bmod i) \times \sum_{j=1}^{m} (m \bmod j)-\sum_{i=1}^{n}(n \bmod i) \times (m \bmod i)$$
看起来就不好搞,用 $a \bmod b=a-\left\lfloor\dfrac{a}{b}\right\rfloor\times b$ 拆了。
$$\sum_{i=1}^{n} (n-\left\lfloor\dfrac{n}{i}\right\rfloor\times i) \times \sum_{j=1}^{m} (m-\left\lfloor\dfrac{m}{j}\right\rfloor\times j)-\sum_{i=1}^{n}(n-\left\lfloor\dfrac{n}{i}\right\rfloor\times i) \times (m-\left\lfloor\dfrac{m}{i}\right\rfloor\times i)$$
看起来就可以用算了分块做,接着拆。
$$(n^2-\sum_{i=1}^{n} \left\lfloor\dfrac{n}{i}\right\rfloor\times i )\times (m^2-\sum_{j=1}^{m} \left\lfloor\dfrac{m}{j}\right\rfloor\times j)-\sum_{i=1}^{n}(n\times m-n\times (\left\lfloor\dfrac{m}{i}\right\rfloor\times i)-m\times(\left\lfloor\dfrac{n}{i}\right\rfloor\times i)+(\left\lfloor\dfrac{n}{i}\right\rfloor\times i\times \left\lfloor\dfrac{m}{i}\right\rfloor\times i))$$
左边的已经可以用数论分块做了,现在看右边那一坨。
$$\sum_{i=1}^{n}(n\times m-n\times (\left\lfloor\dfrac{m}{i}\right\rfloor\times i)-m\times(\left\lfloor\dfrac{n}{i}\right\rfloor\times i)+(\left\lfloor\dfrac{n}{i}\right\rfloor\times i\times \left\lfloor\dfrac{m}{i}\right\rfloor\times i))$$
$$n\times n\times m-n\sum_{i=1}^{n}(\left\lfloor\dfrac{m}{i}\right\rfloor\times i)-m\times \sum_{i=1}^{n}(\left\lfloor\dfrac{n}{i}\right\rfloor\times i)+\sum_{i=1}^{n}(i^2\times \left\lfloor\dfrac{n}{i}\right\rfloor\times \left\lfloor\dfrac{m}{i}\right\rfloor)$$
前面三项可以用数论分块做,最后一项怎么办?
其实还是数论分块,只不过把 `j=n/(n/i)` 换成了 `j=min(n/(n/i),m/(m/i))`,想一想为什么。然后发现,知道了左右边界也不会计算 $\sum\limits_{i=1}^{n}i^2$,~~诶这不是小学 XES 讲过的踢三角证明吗~~,然后我们就应该知道:$\sum\limits_{i=1}^{n}i^2=\dfrac{n(n+1)(2n+1)}{6}$。
然后调亿会儿就好了。
```cpp
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod=19940417;
const int inv2=9970209;
const int inv6=3323403;
ll n,m;
inline ll sum1(ll l,ll r){
return (l+r)*(r-l+1)%mod*inv2%mod;
}
inline ll sum2(ll l,ll r){
return (r*(r+1)%mod*(r+r+1)%mod-(l-1)*l%mod*(l+l-1)%mod)*inv6%mod;
}
ll fn(ll n){
ll ans=0;
for(ll i=1,j;i<=n;i=j+1){
j=n/(n/i);
ans=(ans+(n/i)*sum1(i,j)%mod)%mod;
}
return ans;
}
ll fm(ll m){
ll ans=0;
for(ll i=1,j;i<=n;i=j+1){
j=min(m/(m/i),n);
ans=(ans+(m/i)*sum1(i,j)%mod)%mod;
if(i==n) break;
}
return ans;
}
ll fnm(ll n,ll m){
ll ans=0;
for(ll i=1,j;i<=n;i=j+1){
j=min(n/(n/i),m/(m/i));
ans=(ans+(n/i)*(m/i)%mod*sum2(i,j)%mod)%mod;
}
return ans;
}
int main() {
scanf("%lld%lld",&n,&m);
if(n>m) swap(n,m);
ll tmp1=(n*n%mod-fn(n))%mod,tmp2=(m*m-fn(m))%mod;
ll tmp3=(n*n%mod*m%mod-m*fn(n)%mod-n*fm(m)%mod+fnm(n,m)%mod)%mod;
printf("%lld\n",((tmp1*tmp2%mod-tmp3)%mod+mod)%mod);
return 0;
}
```
### 公式
话说这些公式还是比较有用的吧。
$\sum\limits_{i=1}^{n}i=\dfrac{n(n+1)}{2}
\sum\limits_{i=1}^{n}i^2=\dfrac{n(n+1)(2n+1)}{6}
\sum\limits_{i=1}^{n}i^3=(\sum\limits_{i=1}^{n}i)^2=\dfrac{n^2(n+1)^2}{4}
推荐阅读
浅谈数论分块