参考的资料尽可能都列出来吧:
无前置芝士,但至少你要能看懂本文中出现的所有记号
想不出标题只好这样写标题的屑作者
数论分块是用于处理形如 ∑ i = 1 n ⌊ n i ⌋ \sum\limits_{i=1}^n \left\lfloor\dfrac ni\right\rfloor i=1∑n⌊in⌋ 的式子的方法。
为了简洁和减轻 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 1≤i≤n 时, i i i 的取值有 n \sqrt n n 种,则上式对应的取值也最多只有 n \sqrt n n 种。
当 n < i ≤ n \sqrt n n<i≤n 时, 1 ≤ n / i ≤ n 1 \le n/i \le \sqrt n 1≤n/i≤n ,则式子的取值也最多只有 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 r
如果 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′=r−kd
可以看出 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 1∼n 很自然地被划分成了很多个小块,每个块内 n / i n/i n/i 的值各自相等。
就像分块一样,整个数列被划分成了很多连续的子部分。
那么,一个块的左边界,实际上就是它上一个块的右边界+1。
顺便说一句,一个块可以被表示为 ∑ i = l r k / i \sum\limits_{i=l}^r k/i i=l∑rk/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=1∑nk/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);
}
一道简单的例题:洛谷P2261 [CQOI2007]余数求和
题意:求 ∑ i = 1 n k m o d i \sum\limits_{i=1}^n k\bmod i i=1∑nkmodi 。
众所周知, 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=1∑nkmodi=i=1∑nk−(k/i)×i=n×k−i=1∑n(k/i)×i
看一看文章的标题,我们发现后面的式子要用整除分块处理。
对于它的一个块,按照上文讲的内容可以写成 ∑ i = l r ( k / i ) × i \sum\limits_{i=l}^r(k/i)\times i i=l∑r(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=l∑r(k/i)×i=i=l∑rT×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=l∑rT×i=Ti=l∑ri
后面的式子就是个很简单的等差数列了
那么再化简一发就可以知道这个块的答案是 ( l + r ) ( r − l + 1 ) ( k / l ) 2 \dfrac{(l+r)(r-l+1)(k/l)}2 2(l+r)(r−l+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;
}
一道稍难一些的例题
洛谷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=1∑nj=1∑m[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=1∑nj=1∑m(n%i)(m%j)=(i=1∑n(n%i))(j=1∑m(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=1∑k(n%i)(m%i)=i=1∑k(n−(n/i)×i)(m−(m/i)×i)=i=1∑knm−(n/i)×mi−(m/i)×ni+(n/i)(m/i)i2=knm−(mi=1∑k(n/i)×i)−(ni=1∑k(m/i)×i)−(i=1∑ki2(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=1∑ki2=6k(k+1)(2k+1)
如果你弄懂了上一道例题,其它部分的处理自然也没有问题了
这时我们发现 7 ∣ 19940417 7\mid 19940417 7∣19940417 ,模数不是质数 出题人司马
exgcd 求逆早忘记了,怎么办?
发现 6 ∣ 19940418 6\mid 19940418 6∣19940418 ,那么我们手算一下就能发现 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;
}