数论分块
引入
现在假设你要求这么一个玩意:
这不简单?我直接循环暴力求
显然出题人肯定不会让你暴力求。
使用数论分块可以实现 \(O(\sqrt{n})\) 计算出来。
思路
要知道 \(\lfloor\frac{n}{i}\rfloor\) 这个东西是一个「阶梯式」函数:

可以看到存在一段(好几段)连续的 \(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_{i=l}^{r}f(i)\) 这个东西你用前缀和维护就好了,
然后前面这个 \(\sum_{d\in S}\) 可以证明 \(|S|\le 2\sqrt{n}\) (别着急,后面会证的)。
这样我们只需要知道 \(f(n)\) 的前缀和,以及每个 \(d\) 所对应的区间 \([l,r]\) 就可以以 \(O(\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\) 中的元素从小到大排列后依次是:
三
对于 \(d\in S\),所有满足 \(\big\lfloor\frac{n}{i}\big\rfloor\) 的 \(i\) 取值范围 \([l,r]\) 为:
即
证明
因为 \(\big\lfloor\frac{n}{i}\big\rfloor\) 相当于不等式
将不等式变形,得
因为 \(i\in N_+\) 对不等式取整
过程
以防你把原式忘了
#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
由于这里面有除法,我也不知道算的对不对,仅供参考。
扩展
向上取整的数论分块
现在某人让你求这个玩意:
我们都知道
于是我们把枚举上界变一下,单独提出来一项
这下你就会了。
例题
余数求和\(^{luoguP2261}\)
\(k\bmod i\) 可以变成这样 \(k-\lfloor\frac{k}{i}\rfloor\times i\)
原式就可以化简成:
然后把这个 \(k\) 提出来
后面这个 \(\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}\)
很显然,我们可以设计一个函数:
这样 \(ans=g(Y)-g(X-1)\) 。
然后想想怎么求这个 \(g(x)\)。
我们枚举因数,发现在 \([1,x]\) 中因数有 \(i\) 的数有 \(\big\lfloor\frac{x}{i}\big\rfloor\) 个。
于是
使用数论分块加速即可。
#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;
}

浙公网安备 33010602011771号