数论分块

一、应用情景

\(\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)

题意

求解:

\[\sum_{i=1}^{n} \lfloor \frac n i \rfloor \]

题解

板子,没什么好讲的,\(g(i) = 1\)

只不过你需要注意当 \(n = 2147482647\) 时,\(for\) 循环的 \(i\) 有可能溢出,然后导致 RE/TLE 帖子

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~\)

题解

取模操作显然非常难受,但是我们可以将取模变成减法。

\[\begin{aligned} G(n,k)= & \sum_{i=1}^n k\bmod i\\ = & \sum_{i=1}^n(k - i \times \lfloor \frac k i\rfloor)\\ = & n\times k - \sum_{i=1}^n i \times \lfloor \frac k i\rfloor \end{aligned} \]

显然式子的前半部分可以 \(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] 模积和

题意

\[\sum_{i=1}^{n} \sum_{j=1}^{m} (n \bmod i) \times (m \bmod j), i \neq j \]

mod 19940417 的值

\(1 \leq n,m \leq 10^9\)

题解

我们像上面的第二题一样还是把取模拆开:

\[\begin{aligned} 原式 = &\sum_{i=1}^{n} (n \bmod i) \times\sum_{j=1}^{m} (m \bmod j) - \sum_{i=1}^{\min\{n,m\}} (n \bmod i)\times (m \bmod i)\\ = & \sum_{i=1}^{n} (n - i \lfloor \frac n i \rfloor) \times \sum_{j=1}^{m} (m - j \lfloor \frac m j \rfloor) - \sum_{i=1}^{\min\{n,m\}} (n - i \lfloor \frac n i \rfloor)\times (m - i \lfloor \frac m i \rfloor)\\ = & \sum_{i=1}^{n} (n - i \lfloor \frac n i \rfloor) \times \sum_{j=1}^{m} (m - j \lfloor \frac m j \rfloor) - \sum_{i=1}^{\min\{n,m\}} (nm - mi \lfloor \frac n i \rfloor - n i \lfloor \frac m i \rfloor + i^2 \lfloor \frac n i \rfloor \lfloor \frac m i \rfloor) \end{aligned} \]

我们可以发现,前面的两部分就是两个数论分块的答案分别相乘即可,而最后一部分需要对 \(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\)

经过了上面两个处理之后,我们就可以写出式子:

\[\begin{aligned} & \sum_{i=1}^n\sum_{j=1}^{\lfloor\frac n i\rfloor} \sum_{k=1}^{\lfloor \frac n {ij}\rfloor} (i+j+k)^2 \\ = & \sum_{i=1}^n\sum_{j=1}^{\lfloor\frac n i\rfloor} \sum_{k=1}^{\lfloor \frac n {ij}\rfloor} (i^2+j^2+k^2+2ij+2jk+2ki) \\ = & \sum_{i=1}^n(i^2\sum_{j=1}^{\lfloor\frac n i\rfloor} \lfloor \frac n {ij}\rfloor +\sum_{j=1}^{\lfloor\frac n i\rfloor} \sum_{k=1}^{\lfloor \frac n {ij}\rfloor} (j+k)^2+2i\sum_{j=1}^{\lfloor\frac n i\rfloor} \sum_{k=1}^{\lfloor \frac n {ij}\rfloor} (j+k)) \end{aligned} \]

我们发现 \(\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);
}
posted @ 2023-10-26 21:34  ricky_lin  阅读(46)  评论(0编辑  收藏  举报