整除分块
目录
整除函数
我们定义下取整函数 \(floor(x)=\lfloor x\rfloor\) 表示不大于 \(x\) 的最小整数
另外,定义上取整函数 \(ceil(x)=\lceil x\rceil\) 表示不小于 \(x\) 的最小整数
例如:
\(\lfloor3.1\rfloor=\lfloor3\rfloor=3\)
\(\lceil3.1\rceil=\lceil4\rceil=4\)
我们可以得到两个很显然的性质:
\(\lfloor x\rfloor\leq x<\lfloor x\rfloor+1\)
\(\lceil x\rceil-1<x\leq \lceil x\rceil\)
整除函数的实现
#include<cmath>
...
x=floor(x);//x=ceil(x)
考虑除法情况下的整除函数实现:
x=n/m;//x=floor(n/m)
x=n/m+(n%m!=0)//x=ceil(n/m)
取商除法
对于自然数 \(n,m,k,r(m\neq 0,0\leq r<m)\) 若满足带余除法式:
\(n\div m=k\cdots r\)
则称呼 \(n\) 为被除数, \(m\) 为除数, \(k\) 为商, \(r\) 为余数
显然,我们可以得到:\(k\leq {n\over m}={km+r\over m}=k+{r\over m}<k+{m\over m}=k+1\)
简单来写,就是 \(k\leq {n\over m}<k+1\)
这里有一个很巧妙的转化:考虑到 \(k\in N,\lfloor{n\over m}\rfloor\leq {n\over m}<\lfloor{n\over m}\rfloor+1\)
因此 \(k=\lfloor{n\over m}\rfloor\)
因为本人是 C++ 选手,因此,习惯性的将之写为 \(k=n/m\)
即本人表示一下:在本人后期的贴子中,可能出现的 \(n/m\) 即 \(\lfloor{n\over m}\rfloor\) ,与 \({n\over m}\) 区分
商的个数
对于被除数 \(n\) ,它的商的个数一定不超过 \((2\sqrt n+1)\) 个
证明:
对于除数 \(m\in Z_+\)
若 \(m\leq \sqrt n\) ,则商 \(n/m\) 的取值个数一定不超过 \(m\) 的个数
因此商 \(n/m\) 的取值个数一定不超过 \(\sqrt n\) 个
若 \(m>\sqrt n\) ,则商 \({n\over m}\) 的取值范围为 \([0,\sqrt n)\)
因此 \(n/m\) 的取值个数一定不超过该区间内的整数个数,因此不超过 \((\sqrt n+1)\) 个
两部分相加,因此,商的个数不超过 \((2\sqrt n+1)\) 个
而特殊的,如保证 \(m\leq n\) ,则另外可保证商的个数不超过 \(2\sqrt n\)
即扣除了商为 \(0\) 的情况
整除分块
(除数的范围为 \(1\)~\(n\) )
考虑到被除数 \(n\) 的商个数是 \(O(\sqrt n)\) 级别的
因此当题目需要考虑的是 \(\forall i\in Z_+,\lfloor{n\over i}\rfloor\) 时,我们可以通过枚举这 \((2\sqrt n+1)\) 个商来实现
这样一来,我们可以把时间复杂度从 \(O(n)\) 降低至 \(O(\sqrt n)\)
我们先考虑:假设对于 \(\forall i\in[l,r]\bigcap Z\) 都有 \(\lfloor{n\over l-1}\rfloor\neq \lfloor{n\over l}\rfloor=\lfloor{n\over i}\rfloor=\lfloor{n\over r}\rfloor\neq \lfloor{n\over r+1}\rfloor\)
\(\therefore \displaystyle \sum_{i=l}^r\lfloor{n\over i}\rfloor=\sum_{i=1}^r\lfloor{n\over l}\rfloor=\lfloor{n\over l}\rfloor(r-l+1)\)
那么,若我们能已知所有的 \(l,r\) 即可递推出:\(\displaystyle \sum_{i=1}^n\lfloor{n\over i}\rfloor=\sum\lfloor{n\over l}\rfloor(r-l+1)\)
注意到:\(\lfloor{n\over l-1}\rfloor\neq \lfloor{n\over l}\rfloor=\lfloor{n\over r}\rfloor\neq \lfloor{n\over r+1}\rfloor\)
因此,如果我们知道 \(l\) 或 \(r\) ,就能递推出下一个区间的 \(r\) 或 \(l\)
所以,我们选 \(l\) 或选 \(r\) 的关键就在于:
已知这个区间的 \(l\) 或 \(r\) ,能否推出另一个?如果能的话,就能根据推出的另一端,再推出下一个区间的 \(l\) 或 \(r\) ,然后循环至全部区间推出
我们先考虑已知左端点 \(l\) :
第一个 \(l\) 一定为 \(1\)
而对于区间 \([l,r]\) ,由于 \(\forall i\in[l,r]\bigcap Z\) 都有 \(\lfloor{n\over l-1}\rfloor\neq \lfloor{n\over l}\rfloor=\lfloor{n\over i}\rfloor=\lfloor{n\over r}\rfloor\neq \lfloor{n\over r+1}\rfloor\)
由 \(r=max(i)\) 得出 \({n\over r}=min({n\over i})\geq \lfloor{n\over l}\rfloor\)
所以有 \(r\leq {n\over \lfloor{n\over l}\rfloor}\)
考虑到 \(r\in Z_+\) 故 \(r=\lfloor{n\over \lfloor{n\over l}\rfloor}\rfloor\)
为避免混淆,我们记为 \(r=n/(n/l)\)
再考虑已知右端点 \(r\) :
第一个 \(r\) 一定为 \(n\)
而同上可以得出 \(l=min(i)\) 从而有 \({n\over l}=max({n\over i})< \lfloor{n\over r}\rfloor+1\)
因此有 \(l> {n\over \lfloor{n\over r}\rfloor+1}\)
同样考虑 \(l\in Z_+\) 故 \(l=\lfloor{n\over \lfloor{n\over r}\rfloor+1}+1\rfloor=\lfloor{n\over \lfloor{n\over r}\rfloor+1}\rfloor+1\)
为避免混淆,我们记为 \(l=n/(n/r+1)+1\)
整除分块的实现
由上面的推导可知
从左往右整除分块:
for(int l=1,r;l<=n;l=r+1){
int d=n/l;
r=n/d;
......
}
从右往左整出分块:
for(int r=n,l;r>=1;r=l-1){
int d=n/r;
l=n/(d+1)+1;
......
}
整出分块的一般形式
给定 \(n,k\) 所求式与 \(\forall i\in[1,k]\bigcap Z,\lfloor{n\over i}\rfloor\) 有关
从左往右整出分块:
for(int l=1,r;l<=k;l=r+1){
int d=n/l;
r=Min(n/d,k);
......
}
从右往左整出分块:
for(int r=k,l;r>=1;r=l-1){
int d=n/r;
l=n/(d+1)+1;
......
}
同样的,考虑给定下界、同时给定上下界的整出分块
我们发现,实际上,给定上界的整出分块用从右往左更方便;其余情况(给定下界、给定上下界、范围为 \(1\)~\(n\) )的用从左往右更方便(因为代码更好记)