整除分块
整除分块
前言
因为最近在学习莫比乌斯反演,发现整除分块这个东西几乎是非常必要的,因为是真的好用,可以把一些需要\(O(n)\)的枚举优化到\(O(\sqrt n)\)
正文
什么式子可以用整除分块呢?一般是这样
我们发现(打表或者是自己yy),对于一段连续的区间,\(\lfloor\frac{n}{i}\rfloor\)的值是不变的,那么对于这一段区间,我们就可以跳过,\(O(1)\)计算出这一段区间的值
设n=10
i 1 2 3 4 5 6 7 8 9 10
n/i 10 5 3 2 2 1 1 1 1 1
既然是分块,那么一块的边界是什么呢?
我们设左端点是\(l\),右端点是\(r\),\(i \in[l,r]\),这一块的个数为\(k\),可以知道\(k=\lfloor \frac{n}{i} \rfloor=\lfloor \frac{n}{l} \rfloor\),而\(r=max(i),当(i*k<=n)\),所以\(i<=\frac{n}{k}\),即\(r=\lfloor\frac{n}{\lfloor\frac{n}{l}\rfloor}\rfloor\)
而据dalao分析,时间复杂度是\(O(\sqrt n)\),有了这个范围,我们就可以分块了
Code
inline void init (int ans=0) {
for(int l=1,r,len;l<=n;l=r+1) {
r=n/(n/l),len=r-l+1;
ans+=len*(n/l);
}
}
应用
很多时候,整除分块是配合其他一些函数来用的,such as \(\mu\),\(\phi\)...
当我们的区间跳跃的时候,函数值也会跳跃,所以就要记得乘上这一段区间的函数值,这个时候就需要前缀和优化了
听dalao说,有些恶心的题目会卡线性筛(T飞),这个时候就需要杜教筛,你问我杜教筛是什么?问得好,我也不知道(逃~是真的,以后有时间再补吧)
例题
放几道整除分块的例题
[AHOI2005]约数研究
[CQOI2007]余数求和
洛谷P3935 Calculating
第一题要我们求\(\sum_{i=1}^n {f(i)}\),其中\(f(i)\)表示\(i\)的因子数目
其实这里用筛法\(O(nlogn)\)也能过
for (int i=1;i<=n;i++){
for (int j=i;j<=n;j+=i) cnt[j]++;
sum+=cnt[i];
}
\(i\)是我们枚举的因子,第二种循环枚举因子\(i\)的倍数,这些数字全部都包含\(i\),数组\(cnt[x]\)统计\(x\)的因子个数,最后\(sum+=cnt[]\)得到答案
其实这个\(cnt[]\)数组是可以\(O(1)\)算出来的,假设我们依然枚举因子\(i\),\([1,n]\)内因子\(i\)的倍数有\(\lfloor \frac{n}{i} \rfloor\)个
代码就变成了这样
for (int i=1;i<=n;i++) sum+=n/i;
这个玩意还可以再优化,就是整除分块了
我们目前求的是\(\sum_{i=1}^n\lfloor \frac{n}{i} \rfloor\),这不就是我们刚才学的整除分块最适用的式子吗
Code
#include<bits/stdc++.h>
#define il inline
#define rg register
#define lol long long
#define Min(a,b) (a)<(b)?(a):(b)
#define Max(a,b) (a)?(b)?(a):(b)
using namespace std;
void in(int &ans) {
ans=0; char i=getchar();
while(i<'0' || i>'9') i=getchar();
while(i>='0' && i<='9') ans=(ans<<1)+(ans<<3)+i-'0',i=getchar();
}
int main()
{
int n,ans=0; in(n);
for(rg int l=1,r,len;l<=n;l=r+1) {
r=n/(n/l),len=r-l+1;
ans+=n/l*len;
}
printf("%d\n",ans);
}
第二题也是转换一下题目要求的东西
所求变成了\(\sum_{i=1}^n{}k-\lfloor \frac{k}{i} \rfloor\times i=n\times k-\sum_{i=1}^n\lfloor \frac{k}{i} \rfloor\times i\)
对于我们统计的那一个块\(\lfloor \frac{k}{i} \rfloor\)是不变的,\(i\in [l,r]\),那么可以使用等差数列求和公式
注意一下边界
Code
// luogu-judger-enable-o2
#include<bits/stdc++.h>
#define il inline
#define rg register
#define lol long long
#define Min(a,b) (a)<(b)?(a):(b)
#define Max(a,b) (a)?(b)?(a):(b)
using namespace std;
void in(lol &ans) {
ans=0; char i=getchar();
while(i<'0' || i>'9') i=getchar();
while(i>='0' && i<='9') ans=(ans<<1)+(ans<<3)+i-'0',i=getchar();
}
int main()
{
lol n,m;in(n),in(m); lol ans=n*m;
for(rg lol l=1,r,len;l<=n;l=r+1) {
if(m/l!=0) r=Min(m/(m/l),n);
else r=n; len=(r-l+1);
ans-=(m/l)*len*(l+r)/2;
}
printf("%lld\n",ans);
}
第三题题解
下面是配合莫比乌斯反演的题
[POI2007]ZAP-Queries 题解1
[SDOI2015]约数个数和 题解2
YY的GCD 题解3
博主蒟蒻,随意转载.但必须附上原文链接
http://www.cnblogs.com/real-l/
http://www.cnblogs.com/real-l/