关于质数和约数


质数

质数和合数是针对所有大于 1 的 “自然数” 来定义的(所有小于等于1的数都不是质数)。质数,又叫素数,指的是一个数除了1和它本身没有其他的约数。

试除法求质数

试除法的原理是一个数的约数总是成对出现,因此我们判断一个数是否为质数的时候,只需要判断较小的那一个数能否整除该数就行了


```clike

```cpp
#include<iostream>
using namespace std;
bool IsPrime(int x)
{
	if (x < 2) return false;
	for (int i = 2; i <= x / i; i++)//i<=x/i是最好的写法
	//之前我都是写成sqrt(x),但是如果这样写,每次判断i是否小于sqrt(x)都要调用sqrt这个函数,造成时间的浪费
	//之前也见过i*i<x,这样写法也不好,如果i*i数据超范围了,那么就会变成负数,这样是有问题的
		if (x % i == 0) return false;
	return true;
}
int main()
{
	int n, a;
	cin >> n;
	while (n--)
	{
		cin >> a;
		if (IsPrime(a)) cout << "Yes" << endl;
		else cout << "No" << endl;
	}
	return 0;
}

分解质因数

质因子(质因数)在数论里是指能整除给定正整数的质数。
有几点要注意:

  • 一个数n分解而成的质因数最多只包含一个大于sqrt(n)的质因数(反证法:假设可以分成两个大于sqrt(n)的质因数,这两个质因数相乘大于n,矛盾)
  • 遍历过程中i一定是因数,因为如果i是合数,那么i的因数一定小于i,在之前的遍历过程中[2,i-1]已经出现了,那当前这个合数i早就被分解了。比如数12的因数有1,2,3,4,6,12,当i遍历到2,12/2=6,6/2=3,所以i无法遍历到6和12,6和12的因数在之前就已经出现了,并把6,12拆掉了,所以i遍历过程中一定是质数。(这个就是下面代码里循环的关键原因)
  • 拓展:两个没有共同质因子的正整数称为互质。因为 1 没有质因子,1 与任何正整数(包括 1 本身)都是互质。

题目链接–分解质因数

#include<iostream>
using namespace std;
int main()
{
	int a, n;
	cin >> n;
	while (n--) {
		cin >> a;
		for (int i = 2; i <= a / i; i++) {
			if (a % i == 0) {
				int cnt = 0;
				while (a % i == 0) cnt++, a /= i;
				cout << i << " " << cnt << endl;
			}
		}
		if (a > 1) cout << a << " " << 1 << endl;
		//一个数a分解而成的质因数最多只包含一个大于sqrt(a)的质因数
		cout << endl;
	}
	return 0;
}

筛质数(埃氏筛)

把[2,n−1] 中的所有的数的倍数都标记上,最后没有被标记的数就是质数.
一个数a的因数一定小于等于这个数,那么当对该数进行判断时,如果该数未被标记就说明[2,a-1]都不是该数的因数,那么该数就是质数。

计算朴素筛法(对于每一个数都把它的倍数标记)的时间复杂度:对于一个数a,n个数中是a的倍数的有n/a个
所以 时间复杂度n/2+n/3+n/4+…+n/n=n( 1/2+1/3+1/4+1/5+…+1/n)=nln⁡n+c( c 是欧拉常数,约等于 0.577 左右) ln n<log n
时间复杂度记为 O(nlog n)(注:此处的 log特指以 2 为底)

埃氏筛只用质数去筛
质数定理:1-n中有n/ln n个质数
时间复杂度O(nloglogn)
题目链接–筛质数

#include<iostream>
using namespace std;
const int N = 1e6 + 5;
bool vis[N];
int main()
{
	int n, cnt = 0;
	cin >> n;
	for (int i = 2; i <= n; i++) {
		if (!vis[i]) {
			cnt++;
			for (int j = i+i; j <= n; j += i) vis[j] = true;
		}
	}
	cout << cnt;
	return 0;
}

线性筛

实际应用中一般选择线性筛,为什么呢?
若n≈106n≈106,线性筛和埃氏筛的时间效率差不多,若 n≈107n≈107,线性筛会比埃氏筛快了大概一倍。

线性筛这个算法的核心在于一个数只能被它的最小质因数筛掉。

vis[i * prime[j]] = true;
if (i % prime[j] == 0) break;

这一步是干什么的呢,如果i % prime[j] = = 0,prime[j]<i,prime[j]只能是i的最小质因数,也是i*prime[j]的最小质数(prime里面的质数是由小到大存储的,只有当前的prime[j]能整除i,先前的prime都不能,所有先前的都不是i的因数,而后面的prime[j]也不可能作为i的最小质因数后面的prime比当前的prime[j]大),可以直接break了
当i % prime[j] != 0时,也就是说prime[j]比i的最小质因数小,且prime[j]是prime[j]*i的因数,所以prime[j]就是prime[j]*i的最小质因数

题目链接–筛质数

#include<iostream>
using namespace std;
const int N = 1e6 + 5;
bool vis[N];
int prime[N];
int main()
{
	int n, cnt = 0;
	cin >> n;
	for (int i = 2; i <= n; i++) {
		if (!vis[i]) prime[cnt++] = i;
		for (int j = 0; prime[j] <= n / i; j++) {
		//这里判断条件不需要再加一个j<cnt,因为prime[cnt-1]就是当前的最大质数,如果i不是质
		//数,之前就会break,如果i是质数,那么i==prime[cnt-1],也保证j<cnt;
			vis[i * prime[j]] = true;
			if (i % prime[j] == 0) break;
		}
	}
	cout << cnt;
	return 0;
}

约数

试除法求约数

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
bool cmp(int a, int b) {
	return a < b;
}
int main()
{
	int n, a;
	cin >> n;
	while (n--) {
		vector<int>v;
		cin >> a;
		for(int i=1;i<=a/i;i++)
			if (a % i == 0) {
				v.push_back(i);
				if (i != a / i) v.push_back(a/i);
			}
		sort(v.begin(), v.end(), cmp);
		for (auto x : v) cout << x << " ";
		cout << endl;
	}
	return 0;
}

求约数个数

如果 N = p1^c1 * p2^c2 * … *pk^ck
约数个数: (c1 + 1) * (c2 + 1) * … * (ck + 1)
说一下找个公式怎么推导来的吧

p1…pk是该数的质因数,该数的任何一个因数都可以由任意的质因数构成,也就是说该数的因数可以从p1取任意个乘以p2取任意个…
对于p1的取法有0个1个…c1个,所以共c1+1种
一个数的因数就是对p1,p2,p3…pk任取,所以是 (c1 + 1) * (c2 + 1) * … * (ck + 1)个约数
题目链接–约数的个数

#include<iostream>
#include<map>
using namespace std;
const int M = 1e9 + 7;
int main()
{
	map<int, int>m;
	int n, a;
	cin >> n;
	while (n--) {
		cin >> a;
		for (int i = 2; i <= a / i; i++) {
			while (a % i == 0) m[i]++, a /= i;
		}
		if (a > 1) m[a]++;//这一步不要忘掉,详细参考上面分解质因数那题
	}
	long long cnt = 1;
	for (auto x : m) {
		cnt = cnt * (x.second + 1) % M;
	}
	cout << cnt;
	return 0;
}

约数之和

如果 N = p1^c1 * p2^c2 * … *pk^ck
约数之和: (p1^0 + p1^1 + … + p1^c1) * … * (pk^0 + pk^1 + … + pk^ck)
这个公式把它展开就明白了

主要对代码里面一些细节进行说明

	while (k--) temp = (temp * p + 1) % M;

这一步我们要实现的就是p^0 + p^1 + … + p^c
当temp等于1时,temp=tempp+1=p+1,接着temp=tempp+1=p^ 2+p+1(这是第二步循环,一直到第k部循环temp=p^0 + p^1 + … + p^c
题目链接–约数求和

#include<iostream>
#include<map>
using namespace std;
const int M = 1e9 + 7;
typedef long long ll;
int main()
{
	ll n, a;
	cin >> n;
	map<ll, ll>prime;
	while (n--) {
		cin >> a;
		for (int i = 2; i <= a / i; i++) {
			while (a % i == 0) prime[i]++, a /= i;
		}
		if (a > 1)  prime[a]++;
	}
	ll sum = 1;
	for (auto x : prime) {
		ll k = x.second, p = x.first;
		ll temp = 1;
		while (k--) temp = (temp * p + 1) % M;
		sum = (sum * temp) % M;
	}
	cout << sum;
	return 0;
}

呜呜本来想把基础数论这边合一块,试了一下好长…根本不想看,还是分开吧

posted on 2021-08-03 22:58  不依法度  阅读(253)  评论(0)    收藏  举报

导航