数论分块

引入

现在假设你要求这么一个玩意:

\[\sum_{i=1}^{n}f(i)g\left(\Big\lfloor\frac{n}{i}\Big\rfloor\right) \]

这不简单?我直接循环暴力求
显然出题人肯定不会让你暴力求。
使用数论分块可以实现 \(O(\sqrt{n})\) 计算出来。

思路

要知道 \(\lfloor\frac{n}{i}\rfloor\) 这个东西是一个「阶梯式」函数:
image
可以看到存在一段(好几段)连续的 \(x\) 所对应的 \(y\) 的值是一样的。

\(\lfloor\frac{n}{x}\rfloor\) 所有取值的集合 \(S=\big\{\lfloor\frac{n}{x}\rfloor\big| x\in[1,n]\cap \N \big\}\)
对于每一个元素 \(d\in S\),都对应这一个区间 \([l,r]\) 使 \(\forall x\in[l,r]\cap \N\) 都有 \(\lfloor\frac{n}{x}\rfloor=d\)

那么就可以将上面那个式子变一下:

\[\sum_{d\in S}\sum_{i=l}^{r}\big[f(i)g(d)\big]=\sum_{d\in S}\Big[g(d)\sum_{i=l}^{r}f(i)\Big] \]

这不是成两层循环吗?
不不不,\(\sum_{i=l}^{r}f(i)\) 这个东西你用前缀和维护就好了,
然后前面这个 \(\sum_{d\in S}\) 可以证明 \(|S|\le 2\sqrt{n}\) (别着急,后面会证的)。

这样我们只需要知道 \(f(n)\) 的前缀和,以及每个 \(d\) 所对应的区间 \([l,r]\) 就可以以 \(O(\sqrt n)\) 级的复杂度算出来了。

性质

\[|S|\le 2\sqrt{n} \]

证明

分情况讨论:

  • \(i\le \sqrt {n}\) 时,\(i\) 取值最多 \(\sqrt{n}\) 个,所以,\(\big\lfloor\frac{n}{i}\big\rfloor\) 最多有 \(\sqrt{n}\) 个。
  • \(i>\sqrt{n}\) 时,\(\big\lfloor\frac{n}{i}\big\rfloor\le\frac{n}{i}<\sqrt{n}\),最多有 \(\sqrt{n}\) 个取值。

综上所述,\(|S|\le 2\sqrt{n}\)

\(x=\lfloor\sqrt{n}\rfloor\),将集合 \(S\) 中的元素从小到大排列后依次是:

\[1<2<\cdots<x-1<x\le \Big\lfloor\frac{n}{x}\Big\rfloor<\Big\lfloor\frac{n}{x-1}\Big\rfloor<\cdots<\Big\lfloor\frac{n}{2}\Big\rfloor<\Big\lfloor\frac{n}{1}\Big\rfloor=n \]

对于 \(d\in S\),所有满足 \(\big\lfloor\frac{n}{i}\big\rfloor\)\(i\) 取值范围 \([l,r]\) 为:

\[\Big\lfloor\frac{n}{d+1}+1\Big\rfloor\le i \le \Big\lfloor\frac{n}{d}\Big\rfloor \]

\[[l,r]=\left[\Big\lfloor\frac{n}{d+1}+1\Big\rfloor,\Big\lfloor\frac{n}{d}\Big\rfloor\right] \]

证明

因为 \(\big\lfloor\frac{n}{i}\big\rfloor\) 相当于不等式

\[d\le \frac{n}{i}<d+1 \]

将不等式变形,得

\[\frac{n}{d+1}<i\le\frac{n}{d} \]

因为 \(i\in N_+\) 对不等式取整

\[\Big\lfloor\frac{n}{d+1}+1\Big\rfloor\le i \le \Big\lfloor\frac{n}{d}\Big\rfloor \]

过程

以防你把原式忘了

\[\sum_{i=1}^{n}f(i)g\left(\Big\lfloor\frac{n}{i}\Big\rfloor\right) \]

我们看看之前化简的式子 $$\sum_{d\in S}\Big[g(d)\sum_{i=l}^{r}f(i)\Big]$$ 这个式子便于理解,根据性质三以及一些常识,可以知道这个式子里面的 $d$ 就是 $\big\lfloor\frac{n}{l}\big\rfloor$,所以这个式子还可以继续化简成便于计算的形式: $$\sum\left[g\left(\Big\lfloor\frac{n}{l}\Big\rfloor\right)\sum_{i=l}^{r}f(i)\right]$$ 这个 $l$ 和 $r$ 可以边循环边计算。具体地,这当前区间的 $l$ 就是上一区间的 $r+1$,当前区间的 $r$ 就是当前区间 $\Big\lfloor\frac{n}{\lfloor n/l\rfloor}\Big\rfloor$。 我们设 $$f(x)=x+8$$ $$g(x)=x^2+5x+6$$ 使用整数分块计算 $n=1000$ 时 $\sum_{i=1}^{n}f(i)g\left(\Big\lfloor\frac{n}{i}\Big\rfloor\right)$ 的值。
#include<bits/stdc++.h>
using namespace std;

int f(int x){return x+8;}
int g(int x){return x*x+5*x+6;}
int n=1000,ans=0;
int sum[1010];

signed main(){
	for(int i=1;i<=n;++i) sum[i]=sum[i-1]+f(i);
	for(int l=1,r;l<=n;l=r+1){
		r=n/(n/l);
		ans+=(sum[r]-sum[l-1])*g(n/l);
	}
	cout<<ans;
	return 0;
}

答案:27314136

当然,由于 \(f(x)\) 长得十分好看,可以用一些手法把前缀和去掉,
然后我们可以将 \(n\) 拉到 \(1e15\)\(\bmod 1e9+7\) 的值:

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int mod=1e9+7;

int f(int x){return x+8;}
int g(int x){return x*x+5*x+6;}
int n=1e15,ans=0;

signed main(){
	for(int l=1,r;l<=n;l=r+1){
		r=min(n,n/(n/l));
		ans+=((l+r)*(r-l+1)/2+8*(r-l+1))%mod*g(n/l)%mod;
		ans=(ans%mod+mod)%mod;
	}
	cout<<ans;
	return 0;
}

答案:990965906
由于这里面有除法,我也不知道算的对不对,仅供参考。

扩展

向上取整的数论分块

现在某人让你求这个玩意:

\[\sum_{i=1}^{n}f(i)g\left(\Big\lceil\frac{n}{i}\Big\rceil\right) \]

我们都知道

\[\Big\lceil\frac{n}{i}\Big\rceil=\Big\lfloor\frac{n-1}{i}\Big\rfloor+1 \]

于是我们把枚举上界变一下,单独提出来一项

\[f(n)g(1)+\sum_{i=1}^{n-1}f(i)g\left(\Big\lfloor\frac{n-1}{i}\Big\rfloor+1\right) \]

这下你就会了。

例题

余数求和\(^{luoguP2261}\)

\(k\bmod i\) 可以变成这样 \(k-\lfloor\frac{k}{i}\rfloor\times i\)
原式就可以化简成:

\[G(n,k)=\sum_{i=1}^{n}k-\Big\lfloor\frac{k}{i}\Big\rfloor\times i \]

然后把这个 \(k\) 提出来

\[G(n,k)=n\cdot k-\sum_{i=1}^{n}\Big\lfloor\frac{k}{i}\Big\rfloor\times i \]

后面这个 \(\sum_{i=1}^{n}\big\lfloor\frac{k}{i}\big\rfloor\times i\) 可以用整数分块加速。

#include<bits/stdc++.h>
using namespace std;
#define int long long

int n,k;
int ans=0;

signed main(){
	cin>>n>>k;
	ans=n*k;
	for(int l=1,r;l<=n;l=r+1){
		if(k/l==0) break;
		r=min(k/(k/l),n);
		ans-=(r+l)*(r-l+1)/2*(k/l);
	}
	cout<<ans;
	return 0;
}

约数和\(^{luoguP2424}\)

很显然,我们可以设计一个函数:

\[g(x)=\sum_{i=1}^{x}f(i) \]

这样 \(ans=g(Y)-g(X-1)\)
然后想想怎么求这个 \(g(x)\)
我们枚举因数,发现在 \([1,x]\) 中因数有 \(i\) 的数有 \(\big\lfloor\frac{x}{i}\big\rfloor\) 个。
于是

\[g(x)=\sum_{i=1}^{x}\Big\lfloor\frac{x}{i}\Big\rfloor\times i \]

使用数论分块加速即可。

#include<bits/stdc++.h>
using namespace std;
#define int long long

int n,m;

int g(int x){
	int ans=0;
	for(int l=1,r;l<=x;l=r+1){
		r=x/(x/l);
		ans+=(r+l)*(r-l+1)/2*(x/l);
	}
	return ans;
}

signed main(){
	cin>>n>>m;
	cout<<g(m)-g(n-1);
	return 0;
}

posted @ 2026-08-15 09:41  LZYXT  阅读(3)  评论(0)    收藏  举报