信奥日记——数论(快速幂、埃氏筛、欧拉筛)

前情提要

本人蒟蒻,第一次写博,如有写错的地方请指出,轻喷,神犇可以绕行

正文

1.快速幂

快速幂,顾名思义,快速幂就是快速算底数的n次幂。

//朴素的求幂方法
for(int i = 1; i <= n; i++){
    ans *= x;
}

显而易见,如此朴素的求法的时间复杂度为O(N)

于是OIer们便想出了一个更为省时的方法并取名快速幂

思路

根据我们小学二年级学过的知识可以知道

如果(a = b + c)

则 xa = ab + ac

通过这种方法可以把每一步的指数都分成两半,而相应的底数做平方运算。这样不仅能把大的指数变小,而且可以减少循环次数,达到减小时间复杂度的目的。

所以我们要想求 x^n 时只要 n 能转化为 2^i 次方即可得到 x^n = ((x2)2)^2......。

那么我们只要把 n 转换为二进制即可达成这一条件

举个例子
求3的14次方
14 的二进制为 1110
则 3^14 = 3^8 * 3^4 * 3^2

代码

int powd(int a,int k){//传值 a 为底数 k 为指数
	int ans = 1; //定义一个 ans 变量并将初值赋为 1 用来记录答案
	while(k){//k 不等于 0 
		if(k & 1) ans = ans * a; //计算
		k >>= 1; 
		a = a * a; 
 	}
 	return ans;//返回结果
}

如此一来快速幂的时间复杂度就能由O(N)变为O(log2N),与朴素的求法相比极大的节省了时间。

完整代码(包含取模)

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<ctime>

using namespace std;

inline int read(){//快读
   int s=0,w=1;
   char ch=getchar();
   while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
   while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar();
   return s*w;
}

typedef long long ll;//重定义 long long 为 ll 以方便后面使用

int powd(int a,int k,int mod){
	int res = 1;
	while(k){
		if(k & 1) res = (ll) res * a % mod;
        //运算时强制转换为 long long ,防止res 和 a 均为一个临近 int 边缘的值,导致 res * a 超过 int 上限,出现错误
		k >>= 1;
		a = (ll)a * a % mod; 
 	}
 	return res % mod;
}

int main(){
	int a,k,mod;
	a = read();
	k = read();
	mod = read();
	int ans;
	ans = powd(a,k,mod);
	printf("%d^%d mod %d=%d",a,k,mod,ans);
	return 0;
}

2.埃拉托斯特尼筛法(埃氏筛)

埃氏筛是一种由希腊数学家埃拉托斯特尼所提出的一种简单检定素数的算法。

原理

要得到自然数n以内的全部素数,必须把不大于 √n 的所有素数的倍数剔除,剩下的就是素数。

思路

求 2 到 25 之间的素数

step1 : 列出 2 以后所有的数

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

step2 : 标出目前的第一个素数 2

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

step3 : 划掉 2 的倍数

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

step4 : 如果这个序列中最大数小于最后一个划掉的素数的平方,那么剩下的序列中所有的数都是素数,否则回到step2

25 大于 2 的平方,所以返回step2

step5 : 标出目前的第一个素数 3

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

step6 : 划掉 3 的倍数

……

最终我们在划掉 5 的倍数后

23 小于 5 的平方,跳出循环

目前序列中的每个元素为

2 3 5 7 11 13 17 19 23

全部是素数

代码

void get_primes(int n){//传值 n
	for(int i = 2; i <= n; i++){//从 2 开始循环
		if(!prime[i]){//prime[i] 非 0
			cout << i << ",";//输出目前的第一个素数
			for(int j = i; j <= n/i; j++){//循环 √n 次, n/i等于√n,但不用调用sqrt函数可以节约资源
				prime[j * i] = 1;//打标记
			}
		}
	}
}

完整代码(求 2 到 n 之间的素数)

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<ctime>

using namespace std;

inline int read(){//快读
   int s=0,w=1;
   char ch=getchar();
   while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
   while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar();
   return s*w;
}

const int N = 1e7;

bool prime[N];

void get_primes(int n){
	for(int i = 2; i <= n; i++){
		if(!prime[i]){
			cout << i << ",";
			for(int j = i; j <= n/i; j++){
				prime[j * i] = 1;
			}
		}
	}
}

int main(){
	int n;
	n = read();
	get_primes(n);

	return 0;
}

时间复杂度为
$$
O(N * long(longn))
$$
证明见此 (反正我是没看懂 逃……

3.线性筛

思路

顾名思义,一种针对埃氏筛的优化算法,可以达到O(N)的复杂度

看刚才的埃氏筛不难发现,这里面似乎会对某些数标记了很多次其为合数,那么有没有方法能够省掉这些无意义的步骤呢?

追求时间有限的OIer当然要回答你:有!

只要能让每个合数都只被标记一次,那么时间复杂度就可以降到O(N)了。

所以我们只需要在 i % primes[j] == 0 时跳出循环即可

代码

void get_primes(int n){
	for(int i = 2; i <= n; i++){
		if(!prime[i])
			primes[cnt++] = i;
		for(int j = 0; primes[j] <= n/i; j++){
			prime[i * primes[j]] = 1;
			if(i % primes[j] == 0) break;//关键语句
		}
	}
}

完整代码

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<ctime>

using namespace std;

inline int read(){//快读
   int s=0,w=1;
   char ch=getchar();
   while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
   while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar();
   return s*w;
}

const int N = 1e8+5;

bool prime[N];
int primes[N],cnt = 0;
int ans[N]; 

void get_primes(int n){
	for(int i = 2; i <= n; i++){
		if(!prime[i])
			primes[cnt++] = i;
		for(int j = 0; primes[j] <= n/i; j++){
			prime[i * primes[j]] = 1;
			if(i % primes[j] == 0)
				break;
		}
	}
}

int main(){
	int n = read();
	int q = read(); 
	get_primes(n);
	for(int i = 1;i <= q; i++){
		cin >> n;
		ans[i] = primes[n-1];
	}
	for(int i = 1;i <= q; i++){
		printf("%d\n",ans[i]);
	}
	return 0;
}

$$
如此一来埃氏筛的时间复杂度就可以降到O(N)了
$$

posted @ 2021-07-11 23:11  north-polar-star  阅读(201)  评论(0编辑  收藏  举报