质数
试除法判定质数
这个算法广为人知,这里就不证明了,解释一下 i<=√n 的写法
1、不推荐写成i<=sqrt(n)
首先需要引入头文件#include<cmath>麻烦,其次每次循环都要调用sqrt()函数,速度变慢了;
2、强烈不推荐写成 i*i<=n
如果 i 的值比较大, i*i 极有可能有爆int的风险,影响质数判断且很难debug;
3、强烈推荐用 i<=x / i 写法
不需要调用函数且绝对不会有数值过大的风险
#include <iostream> #include <algorithm> using namespace std; bool is_prime(int x) { if (x < 2) return false; for (int i = 2; i <= x / i; i ++ )//核心操作 if (x % i == 0) return false; return true; } int main() { int n; cin >> n; while (n -- ) { int x; cin >> x; if (is_prime(x)) puts("Yes"); else puts("No"); } return 0; }
分解质因数
本词条由“科普中国”科学百科词条编写与应用工作项目 审核 。
质因子(或质因数)在数论里是指能整除给定正整数的质数。根据算术基本定理,不考虑排列顺序的情况下,每个正整数都能够以唯一的方式表示成它的质因数的乘积。
即:n=p1^a1 * p2^a2 *p3^a3.....pn^an(p为质数,a为幂)
证明1:
循环里面的 i 一定是一个质数:假如 i 是一个合数,那么它一定可以分解成多个质因子相乘的形式,这多个质因子同时也是 x 的质因子且比 i 要小,而比 i 小的数在之前的循环过程中一定是被条件除完了的,所以 i 不可能是合数,只可能是质数
证明2:
if (x > 1) cout << x << ' ' << 1 << endl;
比sqet(n)大的质因子x最多只有1个,因为如果该质因子x数量大于一个,则x*x>n,是不可能的,所以比sqet(n)大的质因子x最多只有1个
那么我们只需要找小于sqrt(n)的所有质因子,然后如果n的值还大于1,说明仍然存在质因子,又因为大于sqrt(n)的质因子只有一个,所以直接输出该值并标记幂为1
#include <iostream> #include <algorithm> using namespace std; void divide(int x) { for (int i = 2; i <= x / i; i ++ ) if (x % i == 0)//见证明1:如果该条件成立,i一定是质数 { int s = 0; while (x % i == 0) x /= i, s ++ ; cout << i << ' ' << s << endl; } if (x > 1) cout << x << ' ' << 1 << endl;//最多只有一个大于平方根下n的质因子(两个相乘就大于n了) cout << endl; } int main() { int n; cin >> n; while (n -- ) { int x; cin >> x; divide(x); } return 0; }
筛质数
诶氏筛法 O(nloglogn)
根据算术基本定理,不考虑排列顺序的情况下,每个正整数都能够以唯一的方式表示成它的质因数的乘积。
反向思考:将2到n中,所有质数的倍数删除,那么剩下的就只有质数
#include <iostream> #include <algorithm> using namespace std; const int N= 1000010; int primes[N], cnt;//存质数与质数个数 bool st[N];//用于装合数 void get_primes(int n) { for (int i = 2; i <= n; i ++ ) { if (!st[i])//如果不是合数(是质数) { primes[cnt ++ ] = i;//存质数 for (int j = i + i; j <= n; j += i)//将质数的倍数(合数)装入st[] st[j] = true; } }//最后primes[]存2到n所有质数,st[]装所有合数 } int main() { int n; cin >> n; get_primes(n); cout << cnt << endl; return 0; }
线性筛法 O(n)
if(i%primes[j]==0) break;
分两种情况:
情况1 i%primes[j]!=0
当i%primes[j]!=0时,说明此时遍历到的primes[j]不是i的质因子,那么只可能是此时的primes[j]小于 i 的最小质因子,所以primes[j]*i的最小质因子就是primes[j];
情况2 i%primes[j]==0
当有i%primes[j]==0时,说明i的最小质因子是primes[j],因此primes[j]*i的最小质因子也就应该是
prime[j],当发现primes[j]是i最小质因子的时候,如果再继续进行的话,我们就把 prime[j+1]*i 这个数筛掉了,虽然这个数也是合数,但是我们筛掉它的时候并不是用它的最小质因数筛掉的,而是利用 prime[j+1] 和 i 把它删掉的,也就是说这个数的最小质因数其实是prime[j],如果我们不在这里退出循环的话,我们会发现有些数是被重复删除了的。
#include <iostream> #include <algorithm> using namespace std; const int N= 1000010; int primes[N], cnt; bool st[N]; void get_primes(int n) { for (int i = 2; i <= n; i ++ ) { if (!st[i]) primes[cnt ++ ] = i; for (int j = 0; primes[j] <= n / i; j ++ )//primes[j]*i<=n 筛掉小于n的合数 { st[primes[j] * i] = true; if (i % primes[j] == 0) break;//保证线性筛质数 } } } int main() { int n; cin >> n; get_primes(n); cout << cnt << endl; return 0; }
约数
试除法求约数
若 i 是N的约数,则 N/i 也是N的约数。换言之,约数总是成对出现的((除了对于完全平方数,√N会单独出现)。
#include <iostream> #include <algorithm> #include <vector> using namespace std; vector<int> get_divisors(int x) { vector<int> res; for (int i = 1; i <= x / i; i ++ ) if (x % i == 0) { res.push_back(i);//存储成对出现的约数 if (i != x / i) res.push_back(x / i);//除了对于完全平方数,√N会单独出现 } sort(res.begin(), res.end()); return res; } int main() { int n; cin >> n; while (n -- ) { int x; cin >> x; auto res = get_divisors(x); for (auto x : res) cout << x << ' '; cout << endl; } return 0; }
约数个数
约数个数定理:
根据约数个数定理,我们只需要分解质因数,然后直接套公式即可
分解质因数如上质数部分
#include <iostream> #include <algorithm> #include <unordered_map> #include <vector> using namespace std; typedef long long LL; const int N = 110, mod = 1e9 + 7; int main() { int n; cin >> n; unordered_map<int, int> primes;//存储质数p和对应的幂a while (n -- ) { int x; cin >> x; for (int i = 2; i <= x / i; i ++ )//分解质因数 while (x % i == 0) { x /= i; primes[i] ++ ; } if (x > 1) primes[x] ++ ; } LL res = 1; for (auto p : primes) res = res * (p.second + 1) % mod;//公式(a1+1)*(a2+1)*....*(ak+1) cout << res << endl; return 0; }
约数之和
例题:正整数360的所有正约数的和是多少?
解:将360分解质因数可得
360=2^3*3^2*5^1
由约数和定理可知,360所有正约数的和为
(2^0+2^1+2^2+2^3)×(3^0+3^1+3^2)×(5^0+5^1)=(1+2+4+8)(1+3+9)(1+5)=15×13×6=1170
可知360的约数有1、2、3、4、5、6、8、9、10、12、15、18、
20、24、30、36、40、45、60、72、90、120、180、360;则它们的和为
1+2+3+4+5+6+8+9+10+12+15+18+20+24+30+36+40+45+60+72+90+120+180+360=1170
总结:
分解质因数后,约数和为
值得注意的是:
while (b -- ) t = (t * a + 1) % mod;
若b=0,t=1;b=1,t=p+1 以此类推
#include <iostream> #include <algorithm> #include <unordered_map> #include <vector> using namespace std; typedef long long LL; const int N = 110, mod = 1e9 + 7; int main() { int n; cin >> n; unordered_map<int, int> primes; while (n -- ) { int x; cin >> x; for (int i = 2; i <= x / i; i ++ )//分解质因数 while (x % i == 0) { x /= i; primes[i] ++ ; } if (x > 1) primes[x] ++ ; } LL res = 1; for (auto p : primes)//套公式 { LL a = p.first, b = p.second; LL t = 1; while (b -- ) t = (t * a + 1) % mod;//求累加 res = res * t % mod;//求累乘 } cout << res << endl; return 0; }
最大公约数
辗转相除法求最大公约数,一行代码,背就完事了
#include <iostream> #include <algorithm> using namespace std; int gcd(int a, int b) { return b ? gcd(b, a % b) : a; } int main() { int n; cin >> n; while (n -- ) { int a, b; scanf("%d%d", &a, &b); printf("%d\n", gcd(a, b)); } return 0; }
值得注意的是,可以调用STL;但是速度比自己手写慢个5倍左右!!
#include<iostream> #include<algorithm> using namespace std;! int n,a,b; int main() { cin >> n; while(n--){ cin >> a >> b; cout << __gcd(a,b) << endl;//调用STL函数 } return 0; }