整除分块入门(更新中)

【正文】

整除分块是个什么东西,他是一种用来高效求
\(\sum_{i=1}^n \lfloor \dfrac{n}{i} \rfloor\) 的方法。这个东西在莫比乌斯反演中会经常遇到,所以还是比较重要。学了这个东西你可以马上去学莫比乌斯反演。

运用整除分块我们可以高效的解决这个问题,时间复杂度为 \(\sqrt{n}\)

先举个例子吧,假设 \(n =25\)
那么式子所有的情况可以列出来如下: (下面表来源于 \(\text{cmd}\) 哥哥)

##################################################  25/1=25 *
########################  25/2=12 *
################  25/3=8 *
############  25/4=6 *
##########  25/5=5 *
########  25/6=4 *
######  25/7=3 *
######  25/8=3
####  25/9=2  *
####  25/10=2
####  25/11=2
####  25/12=2
##  25/13=1 *
##  25/14=1
##  25/15=1
##  25/16=1
##  25/17=1
##  25/18=1
##  25/19=1
##  25/20=1
##  25/21=1
##  25/22=1
##  25/23=1
##  25/24=1
##  25/25=1

按照取值分类发现有九种取值,把每一种取值看成一块,那么就有九块。

################################################## 25
----------
########################  12
----------
################  8
----------
############  6
----------
##########  5
----------
########  4
----------
######  3*2
######
----------
####  2*4
...
####
----------
##  1*12
...
##
----------

那么这么看起来,如果我们知道了可能的取值有几块,并且知道了每一块的块长,那么我们就可以很快的求得最终式子的答案。

假设,我们现在已经知道了一个块的左端点为 \(l\) ,且该块的值为 \(k\) 。如果我们求出了右端点 \(r\)

那么这一块的贡献就为 \((r-l+1) \times k\)

考虑怎么求右端点 \(r\)

可以发现对于这个块中的元素,也即是 \(l \sim r\)间的元素。因为他们的值都为 \(k\) ,所以我们也就是说下面这个式子:

\[\lfloor \dfrac{n}{i} \rfloor = \lfloor \dfrac{n}{l} \rfloor \quad i \in[l,r] \]

那么我们知道我们所求的 \(r\) 就是最大的 $ i$ 满足 \(ik\leq n\) ,此时的 \(i\) 也就是我们所求的 \(r\)

那么就是如下式子:

\[ \left\{ \begin{aligned} k & = \ \lfloor \dfrac{n}{l} \rfloor\\ r & = \ max(i),ik \leq n \\ \end{aligned} \right. \]

也就是说:

\(r= \lfloor \dfrac{n}{k} \rfloor = \lfloor \dfrac{n}{ \dfrac{n} {l}} \rfloor\)

然后你求完 \(r\) ,那么 \(r+1\) 就是新的左端点,然后就可以继续往下求。

根据他人博客知道总共的块值 只有 \(\sqrt{n}\) 种取值,又因为我们的时间复杂度是在依赖块值的。所以,这个东西的复杂度就为 \(\sqrt{n}\)

然后就可以做例题1

例题题解:

首先对于式子 $ k \mod i$ 可以将其变为 $ k- i \dfrac{k}{i} $

那么我们要求的式子就变为了 $ \sum_{i=1}^n k- i \lfloor \dfrac{k}{i} \rfloor $ ,发现可以将一些 $ k$ 提取出来。

所以式子就变成了:

\[k \times n- \sum_{i=1}^n i \lfloor \dfrac{k}{i} \rfloor \]

我们发现,里面的 \(\sum_{i=1}^n \lfloor \dfrac{k}{i} \rfloor\) 其实就是我们所讲的整除分块。然后就可以很快的完成计算,但是有个问题是,我们要求的是 \(\sum_{i=1}^n i \lfloor \dfrac{k}{i} \rfloor\) ,答案还需要乘上一个系数 \(i\)

容易发现,我们的 \(\lfloor \dfrac{k}{i} \rfloor\) 中在不同的 \(i\) 时的取值是有重复的(上面讲了)。

考虑求 \(\lfloor \dfrac{k}{i} \rfloor\) 相等的一块 \([l,r]\) 的贡献,也就是 \(\sum_{i=l}^r i \lfloor \dfrac{k}{i} \rfloor\) 。这个式子有一个更好看的形式,因为它的 \(\lfloor \dfrac{k}{i} \rfloor\) 相等。

所以,这一块的贡献可以写为:

\[[(l)+(l+1)+(l+2)+ {...} +(r-1)+(r) ] \times \lfloor \dfrac{k}{i} \rfloor \]

容易发现前面是一坨等差数列,然后就可以用等差数列求和公式,或者说预处理前缀。但因为这里 \(n\) 较大,所以需要用公式。

代码

#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,k;
int sum(int l,int r){
	return (r-l+1)*(l+r)/2;
}
int calc(int n){
	int ans=0;
	for(int l=1,r;l<=n&&k/l;l=r+1){//k/l有可能为0,这样答案贡献为0,所以不用管
		r=min(k/(k/l),n);
		ans+=(k/l)*sum(l,r);
	}
	return ans;
}
signed main(){
	cin>>n>>k;
	cout<<n*k-calc(n);
	return 0;
}

例题2

题目

例题题解

发现,例题其实就是要我们求 \(\sum_{i=1}^bf(i)-\sum_{i=1}^{a-1}f(i)\)

显然的结论是 \(\sum_{i=1}^n f(i)\) 就是 \(\sum_{i=1}^n i \lfloor \dfrac{n}{i} \rfloor\)

这东西太熟悉了,就是我们上面的那道题的一个子问题,然后就直接整除分块过。

代码

#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,k;
int sum(int l,int r){
	return (r-l+1)*(l+r)/2;
}
int calc(int n){
	int ans=0;
	for(int l=1,r;l<=n&&k/l;l=r+1){//k/l有可能为0,这样答案贡献为0,所以不用管
		r=min(k/(k/l),n);
		ans+=(k/l)*sum(l,r);
	}
	return ans;
}
int x,y;
signed main(){
	cin>>x>>y;
	n=y;k=y;
	int ans1=calc(n);
	n=x-1;k=x-1;
	int ans2=calc(n);
	cout<<ans1-ans2;
	return 0;
}

然后接下来,我们在来思考一些可能题目中会有的情况。

比如说,它如果不是向下取整,而是向上取整,那么我们该怎么办呢?

想一下,如果他问的是:

\[\sum_{i=1}^n \lceil \dfrac{n}{i} \rceil \]

那么我们有没有什么办法去快速的求解呢?

我们可以改变一下成:

然后你就可以继续进行整除分块了。

posted @ 2021-07-08 22:06  Pitiless0514  阅读(209)  评论(3编辑  收藏  举报