Loading

【瞎口胡】基础数学 3 筛法

写在前面

还记得第一篇的内容吗?不记得了可以去复习一下。

这一篇将会用到它们。

Eratosthenes 筛法

译名埃拉托色尼筛法。用于筛选 \([1,n]\) 范围内的素数。

其算法思想是,一个质数的所有倍数都不是质数。每当我们筛选到一个质数 \(k\) 时,我们就把 \(xk (x>1 \and xk \leq n)\) 标记为合数。

bool vis[500010];
int n;
...
scanf("%d",&n);
for(int i=2;i<=n;++i){
    if(!vis[i]){
        for(int j=1;i*j<=n;++j){
        	vis[i*j]=true;
		}
	}
}
// 注意 1 不是质数,因此不能简单地用 !vis[x] 来判断 x 是否为质数,应为 !vis[x]&&x!=1

其时间复杂度为 \(O(n \log \log n)\),近似于线性。证明较为复杂,感兴趣的读者可以自行查阅。

但我们仍然有严格 \(O(n)\) 的算法,这就是我们这篇文章的重点:线性筛。

线性筛法 / 欧拉筛法

质数筛

Eratosthenes 不够优秀的原因之一是一个合数可能被它的多个质因子筛掉。为此,我们引入线性筛法,对于每个合数,它只会被自己的最小质因子筛掉,因此是 \(O(n)\)

假设我们已经知道了 \(\leq i\) 的正整数中,每个数是质数与否。令 \(vis_i\) 表示某个数是否被标记为质数(\(vis_i=1\) 即为合数,反之则为质数),\(\operatorname{prime}_{1},\operatorname{prime}_2,...,\operatorname{prime}_s\) 表示 \(\leq i\) 的质数序列。不妨假设 \(\operatorname{prime}\) 序列单调递增。

对于这样的 \(i\),我们依次将 \(vis_{i \times \operatorname{prime}_j}(i \times \operatorname{prime}_j \leq n \and 1 \leq j \leq s)\) 标记为 \(1\)。如果在这个过程中发现存在某个 \(j\) 使得 \(\operatorname{prime}_j \mid i\),则不再进行后续的标记。

这样做的正确性是有保证的。假设出现 \(\operatorname{prime}_j \mid i\) 时有 \(j=v\),那么有:

  1. 所有 \(j' \leq v\)(注意不是 \(<\))都满足 \(\operatorname{prime}_j'\)\(i \times \operatorname{prime}_{j'}\) 的最小质因子。

    反证法:如果 \(i\) 有比 \(\operatorname{prime}_{j'}\) 更小的质因子,那么当时就会停止,不会再进行标记。

  2. 所有 \(j'>v\) 都满足 \(\operatorname{prime}_v\)\(i \times \operatorname{prime}_{j'}\) 的最小质因子。

    因为有 \(\operatorname{prime}_v \mid i\),而 \(\operatorname{prime}_v<\operatorname{prime}_{j'}\)

这样,被标记的合数一定是被它的最小质因子标记一次。对每个 \(i(2 \leq i \leq n)\) 进行上述操作,就得到了 \(\leq n\) 的正整数中,每个数是质数与否。

而怎样确保一个合数一定被标记过呢?设任意合数 \(x\) 的最小质因子为 \(p\)。观察到,它一定会在 \(i=\dfrac{x}{p}\) 时被筛掉。因为 \(p\)\(x\) 的最小质因子,那么 \(p\) 一定不大于 \(\dfrac{x}{p}\) 的最小质因子,故而在 \(i = \dfrac xp\) 时,不会提前终止标记,一定会标记 \(x\) 为合数。

	for(int i=2;i<=n;++i){
		if(!vis[i]){
			prime[++sum]=i;
		}
		for(int j=1;i*prime[j]<=n&&j<=sum;++j){
			vis[i*prime[j]]=true;
			if(i%prime[j]==0){
				break;
			}
		}
	}

小练习

Statement

\([l,r]\) 中素数的个数,其中 \(1 \leq l \leq r \leq 2^{31}-1,r-l \leq 10^6\)

Solution

观察到 \(x\) 一定存在一个 \(\leq \sqrt x\) 的因子。这启发了我们 —— 我们只需要找到 \(\leq \sqrt {2^{31}-1}\) 的所有质数,然后用这些质数筛掉 \([l,r]\) 的所有合数。

有一个重要的结论:\(n\) 以内的质数个数约为 \(\dfrac{n}{\ln n}\),当 \(n=\left \lfloor \sqrt {2^{31}-1} \right \rfloor\) 时该式约为 \(7 \times 10^3\)。因此复杂度为 \(O(\dfrac{r-l+1}{2}+\dfrac{r-l+1}{3}+\dfrac{r-l+1}{5}...) \leq O((r-l+1) \log (r-l+1))\),可以通过本题。

Code

# include <bits/stdc++.h>
# define int long long

const int N=50010,INF=0x3f3f3f3f;

int l,r;
int n;
int prime[N],cnt;
bool vis[N];
bool larvis[1000010];

inline int read(void){
	int res,f=1;
	char c;
	while((c=getchar())<'0'||c>'9')
		if(c=='-')f=-1;
	res=c-48;
	while((c=getchar())>='0'&&c<='9')
		res=res*10+c-48;
	return res*f;
}
inline void calc(void){
	for(int i=2;i<=N-10;++i){
		if(!vis[i]){
			prime[++cnt]=i;
		}
		for(int j=1;i*prime[j]<=N-10;++j){
			vis[i*prime[j]]=true;
			if(i%prime[j]==0){
				break;
			}
		}
	}
	return;
}
# undef int
int main(void){
# define int long long
//	freopen("pcount.in","r",stdin);
//	freopen("pcount.out","w",stdout);
	l=read(),r=read();
	calc();
	printf("%lld ",cnt); 
	for(int i=1;i<=cnt;++i){
		int p=l/prime[i];
		if(l%prime[i]){
			++p;
		}
		p=std::max(p,2ll);
		for(;p*prime[i]<=r;++p){
			larvis[p*prime[i]-l+1]=true;
		}
	}
	int ans=0;
	for(int i=1;i<=r-l+1;++i){
		ans+=(int)(!larvis[i]);
	}
	printf("%lld",ans-(l==1ll));
	return 0;
}

约数个数 Tau 函数

\(\tau(n)\) 表示 \(n\) 的正约数个数。

\(\tau(n) = \prod\limits_{i=1}^{m} (k_i+1)\),其中 \(n = \prod\limits_{i=1}^{m} p_i^{k_i}\)

在线性筛的过程中,设当前枚举到的数为 \(q = i \times prime_{j}\)。对于 \(n=1\)\(n\) 为质数的情况是平凡的,稍加处理即可。

  • 如果 \(prime_j \nmid i\)

    按照计算式,\(\tau(q) = \tau(i) \times 2\)

  • 如果 \(prime_j \mid i\)

    可以这样想,\(q\) 一定只比 \(i\) 多了「 \(i\) 的所有因子中 \(prime_j\) 次数最大的因子个数」个因子。观察到这样就相当于把 \(prime_j\) 的次数固定了,所以我们不考虑 \(prime_j\) 的次数,即将它看作 \(0\) 次。

    再分类讨论:

    • 如果 \(i\) 中只有一个 \(prime_j\)\(\tau(q) = \tau(i) + \tau(\dfrac{i}{prime_j})\),即将这个 \(prime_j\) 除掉之后 \(prime_j\) 就变为了 \(0\) 次。
    • 如果 \(i\) 中有大于一个 \(prime_j\),那么递归地想,\(q\)\(i\) 多的因子个数和 \(i\)\(\dfrac{i}{prime_j}\) 多的因子个数相同。\(i\)\(\dfrac{i}{prime_j}\)\(\tau(i) - \tau(\dfrac{i}{prime_j})\) 个因子。因此 \(\tau(q) = \tau(i) + (\tau(i)-\tau(\dfrac{i}{prime_j}))\)

    观察到第一种情况中 \(\tau(i) = 2\tau(\dfrac{i}{prime_j})\),仍然可以写作第二种情况。因此我们简化了代码。

    inline void Tao(void){
    	tao[1]=1;
    	for(int i=2;i<=MLIM;++i){
    		if(!vis[i]){
    			prime[++sum]=i;
    			tao[i]=2; // 质数的处理
    		}
    		for(int j=1;i*prime[j]<=MLIM&&j<=sum;++j){
    			vis[i*prime[j]]=true;
    			if(i%prime[j]==0){
    				tao[i*prime[j]]=tao[i]+(tao[i]-tao[i/prime[j]]);
    				break;
    			}else{
    				tao[i*prime[j]]=tao[i]*2;
    			}
    		}
    	}
    	for(int i=1;i<=LIM;++i){
    		printf("%lld ",tao[i]);
    	}
    	return;
    }
    

约数和 Sigma 函数

\(\sigma(n)\) 表示 \(n\) 的正约数和。

\(\sigma(n) = \prod\limits_{i=1}^{m} (\sum \limits_{j=0}^{k_i} p_i^j)\),其中 \(n = \prod\limits_{i=1}^{m} p_i^{k_i}\)

\(\tau\) 的筛法类似。

在线性筛的过程中,设当前枚举到的数为 \(q = i \times prime_{j}\)。对于 \(n=1\)\(n\) 为质数的情况是平凡的,稍加处理即可。

  • 如果 \(prime_j \nmid i\)

    按照计算式,\(\sigma(q) = \sigma(i) \times (prime_j+1)\)

  • 如果 \(prime_j \mid i\)

    分类讨论:

    和筛 \(\tau\) 时的分类讨论类似,只不过需要乘上 \(prime_j\), 即 \(\sigma(q) = \sigma(i)+prime_j \times (\sigma(i)-\sigma(\dfrac{i}{prime_j}))\),因为新增的约数中 \(prime_j\) 的次数增加了。

例题 Codeforces 1512G

# include <bits/stdc++.h>

const int N=10000010,INF=0x3f3f3f3f;
bool vis[N];
int sum[N];
int prime[N],tot;
int T,c;
int minx[N];
inline int read(void){
	int res,f=1;
	char c;
	while((c=getchar())<'0'||c>'9')
		if(c=='-')f=-1;
	res=c-48;
	while((c=getchar())>='0'&&c<='9')
		res=res*10+c-48;
	return res*f;
}
inline void Init(void){
	minx[1]=1;
	sum[1]=1;
	for(int i=2;i<=N-10;++i){
		if(!vis[i]){
			prime[++tot]=i;
			sum[i]=i+1;
		}
		if(sum[i]<=N-10){
			if(!minx[sum[i]]){
				minx[sum[i]]=i;
			} 
		}
		for(int j=1;j<=tot&&i*prime[j]<=N-10;++j){
			vis[i*prime[j]]=true;
			if(i%prime[j]==0){
				sum[i*prime[j]]=sum[i]+(sum[i]-sum[i/prime[j]])*prime[j];
				break;
			}else{
				sum[i*prime[j]]=sum[i]+sum[i]*prime[j];
			}
		}
	}
	return;
}
int main(void){
	Init(); 
	T=read();
	while(T--){
		c=read();
		printf("%d\n",minx[c]?minx[c]:-1); 
	}
	return 0;
}

莫比乌斯 Mu 函数

\(\mu(n) = \begin{cases} 0 & \exists i(p_i>1)\\ 1&n=1 \or \forall i(p_i=1) \and 2 \mid m \\ -1 & \text{otherwise.}\end{cases}\)

其中 \(n=\prod\limits_{i=1}^{m} p_i^{k_i}\)

翻译成人话,如果 \(n\) 有质因子次数大于 \(1\),那么 \(\mu(n)=0\),否则 \(\mu(n)\)\(n\) 的不同质因子个数决定,个数的奇偶分别对应 \(\mu(n)=-1\)\(\mu(n)=1\)

考虑在线性筛的过程中,如果有 \(i \mod prime_j = 0\),那么 \(\mu(q)=0\)。否则 \(\mu(q)=-\mu(i)\),因为此时质因子个数奇偶性变化。

同样,质数和 \(1\) 时的处理是平凡的。

欧拉 Phi 函数

\(\varphi(n)=\sum\limits_{1 \leq i \leq n} [\gcd(n,i)=1]\)

\(1\sim n\) 中和 \(n\) 互质的数的个数。

posted @ 2021-06-08 20:09  Meatherm  阅读(168)  评论(0编辑  收藏  举报