数论分块
一、应用情景
求 \(\sum\limits_{i = 1}^{n} g(i)\lfloor\frac n i\rfloor,n\leq 10 ^{12}\)
二、常见结合
- 莫比乌斯反演
- ……
三、算法原理&代码实现
实际上,\(~\lfloor \frac n i\rfloor~\)的取值其实最多只有\(~2\times\sqrt n~\) 种:
对于\(~i\in [1,\sqrt n]~\):只有\(~\sqrt n~\)个
对于\(~i\in[\sqrt n,n]~\):\(~\lfloor \frac n i\rfloor~\)只有\(\sqrt n\)个
而数论分块的本质就是通过利用这个性质将一个序列按 \(\lfloor \frac n i \rfloor\) 分成若干块
我们对于每一个块,分别算出这个块的 \(g(i)\) 的和,以及这个块对应的 \(\lfloor \frac n i \rfloor\),即可求出答案
通常地,为了快速求解 \(g(i)\) 我们通常会预处理前缀和,在数论分块的时候就可以差分后 \(O(1)\) 求解了
在后文中我们记 \(h(i,j) = \sum\limits_{k = i}^j g(k)\)
时间复杂度:\(~O(\sqrt n)~\)
code
void solve(){
scanf("%lld",&n);
ll ans = 0;
for(int i = 1,j,num; i <= n; i = j + 1){
num = n / i;
j = n / num;
ans += 1ll * num * (j - i + 1);
}
printf("%lld\n",ans);
return;
}
代码的一些细节讲解
变量:
- \(num\):当前的 \(\lfloor \frac n {i\sim j}\rfloor\) 都是它
- \(i,j\):即当前的 \(num\) 值对应的块,\(i\) 为左端点,\(j\) 为右端点
- \(j-i+1\):即 \(h(i,j)\)
块内的计算:
-
我们假设现在知道 \(n\) 和一个块的左端点 \(l\),如何计算当前块对应的值 \(num\) 和这个块的右端点 \(r\) 呢?
-
\(num\) 其实很好求,根据定义就是 \(\lfloor \frac n i\rfloor\)
-
\(r\) 满足两个性质 \(r \times num \leq n~\&\&~(r+1) \times num > n\)
即 \(0\leq n - r \times num < num\),根据这个性质我们显然可以发现 \(r\) 的取值就是 \(\lfloor \frac n {num}\rfloor\)
因为后式的余数显然满足前式的性质
块间的转化:
- 显然就是让后一个块的左端点变成上一个块的 右端点+1 即可
四、练习
① UVA11526 H(n)
题意
求解:
② P3935 Calculating
题意
若 \(x\) 分解质因数结果为 \(x=p_1^{k_1}p_2^{k_2}\cdots p_n^{k_n}\),令 \(f(x)=(k_1+1)(k_2+1)\cdots (k_n+1)\)
求 \(\sum_{i=l}^rf(i)\) 对 \(998\,244\,353\) 取模的结果。
\(l\leq 10^{14}, r \leq 1.6\times 10^{14}\)
题解
显然这道题中的 \(f(x)\) 就是 \(x\) 的因数个数,虽然说可以筛 \(f(x)\) 但是是 \(O(r)\) 的,显然不可取。
我们换一种思路,对询问差分,然后计算每个因数的贡献。
那么 \(\sum_{i=1}^n f(i) = \sum_{i=1}^n \lfloor n i\rfloor\)
上式显然非常地熟悉了,就是数论分块的经典式子,做一个数论分块即可。
code:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll MOD = 998244353;
ll solve(ll n){
ll ans = 0;
for(ll i = 1,j = 0; i <= n; i = j + 1){
ll num = n / i;
j = n / num;
ans = (ans + (j-i+1) * num % MOD) % MOD;
// printf("%lld : [%lld,%lld] : %lld\n",n,i,j,ans);
}
return ans;
}
int main(){
ll l,r;
scanf("%lld%lld",&l,&r);
printf("%lld",(solve(r)-solve(l-1)+MOD) % MOD);
}
③ P2261 [CQOI2007] 余数求和
题意
求解:\(~G(n,k)=\sum\limits_{i=1}^nk~mod~i~\)
题解
取模操作显然非常难受,但是我们可以将取模变成减法。
显然式子的前半部分可以 \(O(1)\) 求解,式子的后半部分整除分块即可。
本题的几个函数分别的值 \(g(i) = i\),\(h(1,i) = \frac {i(i+1)} 2\),\(h(i,j) = h(1,j) - h(1,i-1)\)
code:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll n,k;
int main(){
scanf("%lld%lld",&n,&k);
ll ans=n*k;
for(ll l = 1, r; l <= n; l = r + 1){
if(k / l != 0) r = min(k / (k / l),n);
else r = n;
ans -= (k / l) * (r - l + 1) * (l + r) / 2;
}
printf("%lld",ans);
return 0;
}
④ P2260 [清华集训2012] 模积和
题意
求
mod 19940417 的值
\(1 \leq n,m \leq 10^9\)
题解
我们像上面的第二题一样还是把取模拆开:
我们可以发现,前面的两部分就是两个数论分块的答案分别相乘即可,而最后一部分需要对 \(n,m\) 两部分一起进行数论分块
(就是把这两个数形成的一堆块的分割点集合并在一起,再以这个集合进行一个块的数论分)
code:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll MOD = 19940417;
ll n,m;
inline ll ksm(ll x,ll k){
ll res = 1;
while(k){
if(k&1) res = res * x % MOD;
x = x * x % MOD;
k >>= 1;
}
return res;
}
inline ll sum1(ll l,ll r){
return (__int128)(l + r) * (r - l + 1) / 2 % MOD;
}
inline ll pref2(ll x) {
return (__int128) x * (x+1) * (2*x+1) / 6 % MOD;
}
inline ll sum2(ll l,ll r){
return (pref2(r) - pref2(l-1) + MOD) % MOD;
}
int main(){
scanf("%lld%lld",&n,&m);
ll ans1 = n * n % MOD;
for(ll i = 1,j; i <= n; i = j+1){
ll num = n / i;
j = n / num;
ans1 = (ans1 - sum1(i,j) * num % MOD + MOD) % MOD;
}
ll ans2 = m * m % MOD;
for(ll i = 1,j; i <= m; i = j+1){
ll num = m / i;
j = m / num;
ans2 = (ans2 - sum1(i,j) * num + MOD) % MOD;
}
ll ans3 = n * m % MOD * min(n,m) % MOD;
for(ll i = 1,j; i <= min(n,m); i = j+1){
ll num1 = n / i,num2 = m / i;
j = min(n / num1, m / num2);
ans3 = (ans3
- sum1(i,j) * ((m * num1 + n * num2) % MOD) % MOD
+ sum2(i,j) * num1 % MOD * num2 % MOD + MOD) % MOD;
}
printf("%lld",(ans1*ans2%MOD-ans3+MOD)%MOD);
}
⑤ P3636 曲面
题意
求三维坐标系中,对于所有满足 \(a\leq xyz \leq b\) 的点 \((x,y,z)\),\((x+y+z)^2\) 的和
\(1 \leq a \leq b \leq 3\times 10^8\)
题解
我们考虑还是对询问进行差分,变成求:对于所有满足 \(1\leq xyz \leq n\) 的点 \((x,y,z)\),\((x+y+z)^2\) 的和
我们发现,\(x,y,z\) 可以为负,这是非常难受的,但是我们可以将其限制在正整数范围内,而答案只需要乘 \(4\)(全是正数后,加上偶数个负号的方案数 \(\binom 3 2+\binom 3 0\))
经过了上面两个处理之后,我们就可以写出式子:
我们发现 \(\sum\limits_{j=1}^{\lfloor\frac n i\rfloor} \lfloor \frac n {ij}\rfloor\)、\(\sum\limits_{j=1}^{\lfloor\frac n i\rfloor} \sum\limits_{k=1}^{\lfloor \frac n {ij}\rfloor} (j+k)^2\)、\(\sum\limits_{j=1}^{\lfloor\frac n i\rfloor} \sum\limits_{k=1}^{\lfloor \frac n {ij}\rfloor} (j+k)\) 都是好求的,就是数论分块即可
我们可以先对 \(i\) 做一遍数论分块,对于每个 \(i\),\(j\) 做再做一遍数论分块
美其名曰:数论分块套数论分块
code:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll MOD = 1e4 + 7;
struct P3{
ll a[3];
inline ll operator [] (const int x)const{return a[x];}
P3(ll x = 0,ll y = 0,ll z = 0){
a[0] = x;a[1] = y;a[2] = z;
}
};
ll inv2,inv6;
ll ksm(ll x,ll k){
ll res = 1;
while(k){
if(k&1) res = res * x % MOD;
x = x * x % MOD;
k >>= 1;
}
return res;
}
inline ll sum1(ll l,ll r){
return (l+r) * (r-l+1) % MOD * inv2 % MOD;
}
inline ll pref2(ll x){
return x * (x+1) % MOD * (2*x+1) % MOD * inv6 % MOD;
}
inline ll sum2(ll l,ll r){
return (pref2(r) - pref2(l-1) + MOD) % MOD;
}
inline P3 solve2(ll n){
ll ans0 = 0,ans1 = 0,ans2 = 0;
for(register ll i = 1,j; i <= n; i = j+1){
ll num = n / i;
j = n / num;
ans0 = (ans0 + (j-i+1) * num % MOD) % MOD;
ans1 = (ans1 + sum1(i,j) * num + (j-i+1) * sum1(1,num)) % MOD;
ans2 = (ans2 + (sum2(i,j) * num) + (2 * sum1(i,j) * sum1(1,num)) + (j-i+1) * sum2(1,num)) % MOD;
}
return P3(ans0,ans1,ans2);
}
ll solve(ll n){
ll ans = 0;
for(ll i = 1,j; i <= n; i = j+1){
ll num = n / i;
j = n / num;
P3 s = solve2(num);
ans = (ans + (sum2(i,j) * s[0]) + (2ll * sum1(i,j) * s[1]) + ((j-i+1) * s[2])) % MOD;
}
return ans;
}
int main(){
ll l,r;
inv2 = ksm(2,MOD-2);
inv6 = ksm(6,MOD-2);
scanf("%lld%lld",&l,&r);
// printf("%lld\n",solve(r));
printf("%lld",(solve(r) - solve(l-1) + MOD) * 4ll % MOD);
}
本文来自博客园,作者:ricky_lin,转载请注明原文链接:https://www.cnblogs.com/rickylin/p/17790471.html