数论分块(整除分块)学习笔记

参考的资料尽可能都列出来吧:

  • 整除分块(数论分块) b y   0 x f f f e \sf by~0\color{red}xfffe by 0xfffe
  • 整除分块 b y   p e n g  ⁣ ⁣ −  ⁣ ⁣ y m \sf by~p\color{red}eng\!\!-\!\!ym by pengym
  • 数论分块 b y   h e n r y _ y \sf by~h\color{red}enry\_y by henry_y

无前置芝士,但至少你要能看懂本文中出现的所有记号


Lv.1

想不出标题只好这样写标题的屑作者

数论分块是用于处理形如 ∑ i = 1 n ⌊ n i ⌋ \sum\limits_{i=1}^n \left\lfloor\dfrac ni\right\rfloor i=1nin 的式子的方法。
为了简洁和减轻 LaTeX \LaTeX LATEX 渲染的负担,下文中会用形如 i / j i/j i/j 的式子表示 ⌊ j i ⌋ \lfloor\frac ji\rfloor ij

显然这个式子可以 Θ ( n ) \Theta(n) Θ(n) 暴力求出,但是实际上有优秀得多的 Θ ( n ) \Theta(\sqrt n) Θ(n ) 解法。

首先我们证明: n / i n/i n/i 的取值最多只有 2 n 2\sqrt n 2n 种。
1 ≤ i ≤ n 1 \le i \le \sqrt n 1in 时, i i i 的取值有 n \sqrt n n 种,则上式对应的取值也最多只有 n \sqrt n n 种。
n < i ≤ n \sqrt n n <in 时, 1 ≤ n / i ≤ n 1 \le n/i \le \sqrt n 1n/in ,则式子的取值也最多只有 n \sqrt n n 种。
综上,命题成立。

那么,如果我们能枚举 n / i n/i n/i 的值求得答案,算法的时间复杂度就可以达到 Θ ( n ) \Theta(\sqrt n) Θ(n )
现在的问题就是,对于 n / i n/i n/i 的一个取值(设为 k k k),满足 n / i = k n/i=k n/i=k i i i 有多少个。

考虑用数学方法确定 i i i 的范围。
实际上, i i i 的上限为 n / ( n / i ) n/(n/i) n/(n/i) 。可以感性理解,但下面还是给出证明:
i i i 的某个取值满足 n / i = k n/i=k n/i=k i i i 的最大值为 i ′ i' i ,那么有 k i + r = n ( 0 ≤ r < k ) ki+r=n(0\le rki+r=n(0r<k)
如果 n / ( i + d ) = k n/(i+d)=k n/(i+d)=k ,那么有 k ( i + d ) + r ′ = n k(i+d)+r'=n k(i+d)+r=n ,两式相减即得 r ′ = r − k d r'=r-kd r=rkd
可以看出 d d d 的最大值 d m a x = r / k d_{max}=r/k dmax=r/k
注意到 i ′ i' i 也可以写成 i + d i+d i+d 的形式,又因为 i ′ i' i i i i 的最大值,所以

i ′ = i + d m a x = i + r / k = i + ⌊ n   m o d   i n / i ⌋ = i + ⌊ n − ( n / i ) × i n / i ⌋ \begin{aligned} i' &= i+d_{max} \\ &= i+r/k \\ &= i+\left\lfloor \dfrac{n \bmod i}{n/i} \right\rfloor \\ &= i+\left\lfloor \dfrac{n-(n/i)\times i}{n/i}\right\rfloor \end{aligned} i=i+dmax=i+r/k=i+n/inmodi=i+n/in(n/i)×i

这时就比较显然了,用基本的通分技巧就能得出化简结果 ⌊ n n / i ⌋ \left\lfloor \dfrac n{n/i}\right\rfloor n/in ,也就是 n / ( n / i ) n/(n/i) n/(n/i)

现在的问题就是,对于 n / i n/i n/i 的一个取值(设为 k k k),满足 n / i = k n/i=k n/i=k i i i 有多少个。

这是我们最初的问题,那么 i i i 的上限就是 n / k n/k n/k
下限相对就好处理得多。按照 n / i n/i n/i 的取值, 1 ∼ n 1\sim n 1n 很自然地被划分成了很多个小块,每个块内 n / i n/i n/i 的值各自相等。
就像分块一样,整个数列被划分成了很多连续的子部分。
那么,一个块的左边界,实际上就是它上一个块的右边界+1。
顺便说一句,一个块可以被表示为 ∑ i = l r k / i \sum\limits_{i=l}^r k/i i=lrk/i

于是我们可以写出极为简洁的核心代码:

	t=sqrt(n); //n/i的上界,l,r初值为0
	for (int i=1; i<=t; ++i) { //这里的i其实对应上文中的n/i
		l=r+1; r=n/i;
		ans+=1ll*i*(r-l+1); //直接累加答案
	}

我们可以直接把 l l l 作为上文中 i i i 的一个取值,可以写出如下代码:

	for (int l=1,r,t; l<=n; l=r+1)
		r=n/(t=n/l),ans+=1ll*t*(r-l+1);
	//(t=n/l)将t的值赋为n/l,并返回该值
	//这样写是为了减少一次做除法的次数

也有很多题目求的是 ∑ i = 1 n k / i \sum\limits_{i=1}^nk/i i=1nk/i 这样的式子
化简过程基本一致,但是分块依据变成了 k / i k/i k/i 的值
注意这个时候 k / l k/l k/l 是可以等于0的,k/(k/l) 这个式子可能会炸
所以一定要特判一下
对于第二份代码可以这样写:

	for (int l=1,r,t; l<=n; l=r+1) {
		r=(t=k/l)?min(k/t,n):n;
		ans+=1ll*t*(r-l+1);
	}

Lv.2

一道简单的例题:洛谷P2261 [CQOI2007]余数求和
题意:求 ∑ i = 1 n k   m o d   i \sum\limits_{i=1}^n k\bmod i i=1nkmodi

众所周知, k   m o d   i = k − ( k / i ) × i k\bmod i=k-(k/i)\times i kmodi=k(k/i)×i,所以
∑ i = 1 n k   m o d   i = ∑ i = 1 n k − ( k / i ) × i = n × k − ∑ i = 1 n ( k / i ) × i \sum\limits_{i=1}^nk\bmod i=\sum\limits_{i=1}^nk-(k/i)\times i=n\times k-\sum\limits_{i=1}^n(k/i)\times i i=1nkmodi=i=1nk(k/i)×i=n×ki=1n(k/i)×i
看一看文章的标题,我们发现后面的式子要用整除分块处理。
对于它的一个块,按照上文讲的内容可以写成 ∑ i = l r ( k / i ) × i \sum\limits_{i=l}^r(k/i)\times i i=lr(k/i)×i
注意到在一个块内 k / i k/i k/i 的值全部相等。为了突出它是个定值,我们设 T = k / i T=k/i T=k/i
于是 ∑ i = l r ( k / i ) × i = ∑ i = l r T × i \sum\limits_{i=l}^r(k/i)\times i=\sum\limits_{i=l}^rT\times i i=lr(k/i)×i=i=lrT×i
再次强调 T T T 是个定值,可以看成常数,那么
∑ i = l r T × i = T ∑ i = l r i \sum\limits_{i=l}^rT\times i=T\sum\limits_{i=l}^ri i=lrT×i=Ti=lri
后面的式子就是个很简单的等差数列了
那么再化简一发就可以知道这个块的答案是 ( l + r ) ( r − l + 1 ) ( k / l ) 2 \dfrac{(l+r)(r-l+1)(k/l)}2 2(l+r)(rl+1)(k/l)

不过,在估计答案大小的时候,最好还是看最初的式子
可以算出答案不会爆long long,但会爆int,所以开long long就行了
另外为了减小常数,除以2可以最后一起除

#include
int n,k; long long ans;
inline int min(int x,int y) { return (x<y?x:y); }
int main() {
	scanf("%d%d",&n,&k);
	for (int l=1,r,t; l<=n; l=r+1) {
		r=(t=k/l)?min(k/t,n):n;
		ans+=1ll*t*(r-l+1)*(l+r);
	}
	printf("%lld\n",1ll*n*k-(ans>>1));
	return 0;
}

Lv.3

一道稍难一些的例题
洛谷P2260 [清华集训2012]模积和

∑ i = 1 n ∑ j = 1 m [ i ≠ j ] ( n   m o d   i ) ( m   m o d   j ) \sum_{i=1}^n\sum_{j=1}^m[i\ne j](n \bmod i)(m \bmod j) i=1nj=1m[i=j](nmodi)(mmodj)

求的是这个式子膜19940417的值(别看了,这数字没啥奇怪的含义)

写许多   m o d   \bmod mod 麻烦,所以下面全部写作 % \% %
考虑没有 i ≠ j i\ne j i=j 限制的情况:

∑ i = 1 n ∑ j = 1 m ( n % i ) ( m % j ) = ( ∑ i = 1 n ( n % i ) ) ( ∑ j = 1 m ( m % j ) ) \sum_{i=1}^n\sum_{j=1}^m(n\% i)(m\%j)=\left(\sum_{i=1}^n(n\% i)\right)\left(\sum_{j=1}^m(m\%j)\right) i=1nj=1m(n%i)(m%j)=(i=1n(n%i))(j=1m(m%j))

这个式子的值用上一道例题的方法就可以算出来
下面计算 i = j i=j i=j 的情况,设 k = min ⁡ ( n , m ) k=\min(n,m) k=min(n,m)

∑ i = 1 k ( n % i ) ( m % i ) = ∑ i = 1 k ( n − ( n / i ) × i ) ( m − ( m / i ) × i ) = ∑ i = 1 k n m − ( n / i ) × m i − ( m / i ) × n i + ( n / i ) ( m / i ) i 2 = k n m − ( m ∑ i = 1 k ( n / i ) × i ) − ( n ∑ i = 1 k ( m / i ) × i ) − ( ∑ i = 1 k i 2 ( n / i ) ( m / i ) ) \begin{aligned} \rm \sum_{i=1}^k(n\%i)(m\%i) &= \rm \sum_{i=1}^k(n-(n/i)\times i)(m-(m/i)\times i) \\ &=\rm \sum_{i=1}^k nm-(n/i)\times mi-(m/i)\times ni +(n/i)(m/i)i^2 \\ &=\rm knm-\left(m\sum_{i=1}^k(n/i)\times i\right)-\left(n\sum_{i=1}^k(m/i)\times i\right)-\left(\sum_{i=1}^k i^2(n/i)(m/i)\right) \end{aligned} i=1k(n%i)(m%i)=i=1k(n(n/i)×i)(m(m/i)×i)=i=1knm(n/i)×mi(m/i)×ni+(n/i)(m/i)i2=knm(mi=1k(n/i)×i)(ni=1k(m/i)×i)(i=1ki2(n/i)(m/i))

附公式: ∑ i = 1 k i 2 = k ( k + 1 ) ( 2 k + 1 ) 6 \sum\limits_{i=1}^ki^2=\dfrac{k(k+1)(2k+1)}6 i=1ki2=6k(k+1)(2k+1)
如果你弄懂了上一道例题,其它部分的处理自然也没有问题了

这时我们发现 7 ∣ 19940417 7\mid 19940417 719940417 ,模数不是质数 出题人司马
exgcd 求逆早忘记了,怎么办?
发现 6 ∣ 19940418 6\mid 19940418 619940418 ,那么我们手算一下就能发现 i n v ( 6 , 19940417 ) = 3323403 inv(6,19940417)=3323403 inv(6,19940417)=3323403 ,于是就不需要 exgcd 了
时间复杂度 Θ ( n ) \Theta(\sqrt n) Θ(n ) ,虽然常数略大但能无压力通过。

放上自己的AC代码,但不建议参考(胡乱使用短路运算符),还是自己码为好
注意计算过程中不要爆int或者long long

#include
#define F(n) for (int l=1,r; l<=n; l=r+1)
const int P=19940417; int n,m,k,s1,s2,s,t;
inline void add(int &x,int y) { (x+=y)>=P&&(x-=P); } 
inline int min(int x,int y) { return (x<y?x:y); }
inline int S2(int l,int r) {
	int x=1ll*l*(l+1)%P*(l<<1|1)%P;
	int y=1ll*r*(r+1)%P*(r<<1|1)%P;
	return 1ll*(y-x+P)%P*3323403%P;
}
int main() {
	scanf("%d%d",&n,&m); k=min(n,m);
	F(n) r=n/(t=n/l),add(s1,(1ll*(l+r)*(r-l+1)>>1)%P*t%P);
	F(m) r=m/(t=m/l),add(s2,(1ll*(l+r)*(r-l+1)>>1)%P*t%P);
	s=(1ll*n*n-s1+P)%P*((1ll*m*m-s2+P)%P)%P;
	((s-=1ll*n*m%P*k%P)%=P)<0&&(s+=P);
	F(k) {
		r=min(n/(s1=n/l),m/(s2=m/l));
		t=(1ll*n*s2+1ll*m*s1)%P;
		t=(1ll*(l+r)*(r-l+1)>>1)%P*t%P;
		((t-=1ll*s1*s2%P*S2(l-1,r)%P)%=P)<0&&(t+=P);
		add(s,t);
	}
	printf("%d",s);
	return 0;
}

你可能感兴趣的:(学习笔记,算法讲解)