整除分块学习笔记
搬运自我的洛谷文章整除分块学习笔记。
前言
本文是作者学习整除分块后的一些心得,可能有所不足,如有错误,望读者指正。
分块技巧
本文以求解问题:
为例讲述整除分块。
易知当 \(\left\lfloor\dfrac{n}{i}\right\rfloor=d\) 且 \(n,d\) 一定时,\(i\) 的取值范围是一段连续的区间,记作 \([L,R]\)。
假设 \(L\) 一定,如何快速求出 \(R\) 呢?
令 \(\left\lfloor\dfrac{n}{L}\right\rfloor=d\),求最大的 \(\Delta\),此时 \(R=L+\Delta\) 且 \(\left\lfloor\dfrac{n}{L}\right\rfloor=\left\lfloor\dfrac{n}{L+\Delta}\right\rfloor=d\)。
将两个式子化为整式可得 \(Ld+r_1=(L+\Delta)d+r_2=n\),其中 \(0\leq r_1,r_2<d\)。两式相减得 \(\Delta d=r_1-r_2\)。
\(\because \Delta d=r_1-r_2\)
\(\therefore r_2=r_1-\Delta d\)
又 \(\because r_2\geq0\)
\(\therefore r_1\geq\Delta d\)
\(\therefore \Delta\leq\left\lfloor\dfrac{r1}{d}\right\rfloor\)
又 \(\because r_1=n \bmod L=n-L\times\left\lfloor\dfrac{n}{L}\right\rfloor,d=\left\lfloor\dfrac{n}{L}\right\rfloor\)
\(\therefore \Delta\leq\left\lfloor\dfrac{n-L\times\left\lfloor\dfrac{n}{L}\right\rfloor}{\left\lfloor\dfrac{n}{L}\right\rfloor}\right\rfloor\)
\(\therefore \Delta_{\max}\\=\left\lfloor\dfrac{n-L\times\left\lfloor\dfrac{n}{L}\right\rfloor}{\left\lfloor\dfrac{n}{L}\right\rfloor}\right\rfloor\\=\left\lfloor\dfrac{n}{\left\lfloor\dfrac{n}{L}\right\rfloor}\right\rfloor-L\)
\(\therefore R=L+\Delta_{\max}=\left\lfloor\dfrac{n}{\left\lfloor\dfrac{n}{L}\right\rfloor}\right\rfloor\)
然后 \(L\) 的初始值从 \(1\) 开始,\(R\) 根据 \(L\) 推导,接着之后的 \(L=R+1\)。
显然当 \(L\leq \sqrt{n}\) 时 \(L\) 有 不超过\(\sqrt{n}\) 种取值,当 \(L>\sqrt{n}\) 时 \(\dfrac{n}{L}\) 不超过有 \(\sqrt{n}\) 种取值,所以 \(L\) 共有不超过 \(2\sqrt{n}\) 种取值,加上 \(L,R\) 的计算均为 \(\mathcal{O(1)}\) 级别的,所以整个算法的时间复杂度就是 \(\mathcal{O(\sqrt{n})}\),算是较为优秀的了。
例题
P2261 [CQOI2007] 余数求和
简单推导即可:
\(G(n,k)=\sum_{i=1}^n k\bmod i=n\times k-\sum_{i=1}^n i\times\lfloor\dfrac{k}{i}\rfloor\)。
用等差数列结合整除分块做即可。
\(\text{code}\):
#include<bits/stdc++.h>
#define int unsigned long long
using namespace std;
int n,k,L=1,R,ans;
signed main(){
cin>>n>>k,ans=n*k;
while(L<=n){
if(k/L>0)R=min(k/(k/L),n);
else R=n;//跳右端点
ans-=(R-L+1)*(L+R)/2*(k/L),L=R+1;//等差数列求和
}
cout<<ans;
return 0;
}
P6583 回首过去
非常有学习意义的一题。
考虑将 \(\dfrac{x}{y}\) 转化为 \(\dfrac{b\times c}{a\times c}\),其中 \(a\) 只包含因数 \(2,5\) 且 \(\gcd(c,10)=1\)。
我们考虑枚举合法的 \(c\) 并求出同样合法的 \(a,b\) 的情况数。
显然 \(c \in [1,n]\) 且 \(\gcd(10,c)=1\) 时,\(b\in[1,\lfloor\frac{n}{c}\rfloor]\),而 \(a\) 则可以是 \([1,\lfloor\frac{n}{c}\rfloor]\) 中只含因数 \(2,5\) 的数,将所有合法的情况数记为 \(f(\lfloor\frac{n}{c}\rfloor)\)。
故总答案为 \(f(\lfloor\frac{n}{c}\rfloor)\times \lfloor\frac{n}{c}\rfloor\) 乘上 \(c\) 的取值方案数。
但 \(n\leq10^{12}\),显然,这个算法还是不够优秀。
等等,\(\left\lfloor\frac{n}{c}\right\rfloor\),这个不是整除分块吗?我们显然可以求出 \([L,R]\) 使得 \(\lfloor\frac{n}{L}\rfloor=\lfloor\frac{n}{R}\rfloor=k\),那么时间复杂度不就降至 \(\sqrt{n}\) 级别了吗?
但是还有问题,我们无法直接通过 c%2!=0&&c%5!=0 判断 \(c\) 是否合法了。
那怎么办?通过容斥原理可以解决。
\([L,R]\) 中合法的 \(c\) 可以这样计算:\([L,R]\) 中 \(1\) 的倍数 \(-\) \([L,R]\) 中 \(2\) 的倍数 \(-\) \([L,R]\) 中 \(5\) 的倍数 \(+\) \([L,R]\) 中 \(10\) 的倍数,记为 \(f'([L,R])\)。
但是这样的函数并不是很好求。但是将 \(f'([L,R])\) 换一种形式,变为 \(f'([1,R])-f'([1,L])\) 就可以通过除法直接求得了。
所以问题就迎刃而解了。
\(\text{code}\):
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,L=1,res;
int g1(int x){return x-x/2-x/5+x/10;}//求c的取值方案数
int g2(int x){//求1~c内a的取值方案数
int ans=0;
for(int i=1;i<=x;i<<=1)for(int j=i;j<=x;j*=5)ans++;
return ans;
}
signed main(){
cin>>n;
while(L<=n){
int R=n/(n/L);
res+=(g1(R)-g1(L-1))*g2(n/L)*(n/L),L=R+1;
}
cout<<res;
return 0;
}

浙公网安备 33010602011771号