质数
质数定义:一个大于1的自然数,除了1和它本身外,不能被其他自然数整除,换句话说就是该数除了1和它本身以外不再有其他的因数,这个数就是质数。
1.质数的判定--试除法
给定一个数 x,判断 x 是否为质数:
用 x 除以 2 ~ x - 1 中的每个数,如果出现了余数为 0,则这个数不是质数,如果没有出现余数为 0,则这个数是质数。
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;
} 时间On
优化:
一个数的因数都是成对出现的:例如12的因数有3和4,2和6
所以我们可以只枚举较小的那一个,即根下n,假设较小的为d,较大的为n/d;
有性质:若 d|n,则 n/d|n。
例题:试除法判定质数
给定 n个正整数 ai,判定每个数是否是质数。
输入格式
第一行包含整数 n。
接下来 n行,每行包含一个正整数 ai。
输出格式
共 n 行,其中第 i行输出第 i 个正整数 ai 是否为质数,是则输出 Yes
,否则输出 No
。
数据范围
1≤n≤100,
1≤ai≤2的31次方-1
输入样例:
2
2
6
输出样例:
Yes
No
#include<bits/stdc++.h>
using namespace std;
bool is_prime(int n){
if(n<2) return false;
for(int i=2;i<=n/i;i++){
if(n%i==0) return false;
}
return true;
}
int main(){
int n;
cin>>n;
for(int i=0;i<n;i++){
int a;
cin>>a;
if(is_prime(a)) cout<<"Yes"<<endl;
else cout<<"No"<<endl;
} 时间O根号n
}
作者:TaoZex
链接:https://www.acwing.com/solution/content/3368/
来源:AcWing
2.分解质因数--试除法
试除法分解质因数:
n=pα11…pαkk
void divide(int x)
{
for (int i = 2; i <= x / i; i ++ )
if (x % i == 0) //若此条件成立,i一定是质数
{
int s = 0; //s为次数
while (x % i == 0) x /= i, s ++ ;
cout << i << ' ' << s << endl;
}
if (x > 1) cout << x << ' ' << 1 << endl; //单独处理,唯一一个大于sqrt(n)的质因子
cout << endl;
}
注:由于 n不包含任何从 2到 i−1 之间的质因子(已经被除干净了),若x % i = 0成立,i不包含其中的所有质因子,故i一定是质数;
n中最多只包含一个大于sqrt(n)的质因子;
当n = 2^k时,时间复杂度为O(log n),而最坏时间复杂度为O(sqrt(n)),平均情况是介于两者之间。
x 的质因子最多只包含一个大于 根号x 的质数。如果有两个,这两个因子的乘积就会大于 x,矛盾。
i 从 2 遍历到 根号x。 用 x / i,如果余数为 0,则 i 是一个质因子。
s 表示质因子 i 的指数,x /= i 为 0,则 s++, x = x / i 。
最后检查是否有大于 根号x 的质因子,如果有,输出。
例题:分解质因数
给定 n 个正整数 ai,将每个数分解质因数,并按照质因数从小到大的顺序输出每个质因数的底数和指数。
输入格式
第一行包含整数 n。
接下来 n 行,每行包含一个正整数 ai。
输出格式
对于每个正整数 ai,按照从小到大的顺序输出其分解质因数后,每个质因数的底数和指数,每个底数和指数占一行。
每个正整数的质因数全部输出完毕后,输出一个空行。
数据范围
1≤n≤100,
2≤ai≤2×10的9次方
输入样例:
2
6
8
输出样例:
2 1
3 1
2 3
#include <iostream>
#include <algorithm>
using namespace std;
void divide(int x)
{
for (int i = 2; i <= x / i; i ++ )//i <= x / i:防止越界,速度大于 i < sqrt(x)
if (x % i == 0)//i为底数
{
int s = 0;//s为指数
while (x % i == 0) x /= i, s ++ ;
cout << i << ' ' << s << endl;//输出
}
if (x > 1) cout << x << ' ' << 1 << endl;//如果x还有剩余,单独处理
cout << endl;
}
int main()
{
int n;
cin >> n;
while (n -- )
{
int x;
cin >> x;
divide(x);
}
return 0;
} 时间复杂度可能为根n
作者:Hasity
链接:https://www.acwing.com/solution/content/27448/
来源:AcWing
3.筛质数
1.朴素筛法
不断筛掉所有数的倍数
1.最普通的筛法 O(nlogn)
void get_primes2(){
for(int i=2;i<=n;i++){
if(!st[i]) primes[cnt++]=i;//把素数存起来
for(int j=i;j<=n;j+=i){//不管是合数还是质数,都用来筛掉后面它的倍数
st[j]=true;
}
}
}
2.优化版就是只删质数的倍数
质数定理:1-n中只有 n/ logn个质数
诶氏筛法 O(nloglogn)
void get_primes1(){
for(int i=2;i<=n;i++){
if(!st[i]){
primes[cnt++]=i;
for(int j=i;j<=n;j+=i) st[j]=true;//可以用质数就把所有的合数都筛掉;
}
}
}
3.线性筛法
n 只会被它的最小质因子筛掉:
i % primes[j] == 0:primes[j]一定是i的最小质因子,primes[j]一定是primes[j] * i的最小质因子
i % primes[j] != 0:primes[j]一定小于i的最小质因子,primes[j]也一定是primes[j] * i的最小质因子
对于一个合数 x,假设 primes[j] 是 x 的最小质因子,当 i 枚举到 x / primes[j] 时,将会筛掉,每个合数只会被筛掉一次。
int primes[N], cnt; // primes[]存储所有素数
bool st[N]; // st[x]存储x是否被筛掉
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 ++ )
{
st[primes[j] * i] = true;
if (i % primes[j] == 0) break; // primes[j]一定是i的最小质因子
}
}
}
void get_primes(){
//外层从2~n迭代,因为这毕竟算的是1~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]<=n/i:变形一下得到——primes[j]*i<=n,把大于n的合数都筛了就
//没啥意义了
st[primes[j]*i]=true;//用最小质因子去筛合数
//1)当i%primes[j]!=0时,说明此时遍历到的primes[j]不是i的质因子,那么只可能是此时的primes[j]<i的
//最小质因子,所以primes[j]*i的最小质因子就是primes[j];
//2)当有i%primes[j]==0时,说明i的最小质因子是primes[j],因此primes[j]*i的最小质因子也就应该是
//prime[j],之后接着用st[primes[j+1]*i]=true去筛合数时,就不是用最小质因子去更新了,因为i有最小
//质因子primes[j]<primes[j+1],此时的primes[j+1]不是primes[j+1]*i的最小质因子,此时就应该
//退出循环,避免之后重复进行筛选。
if(i%primes[j]==0) break;
}
}
}
例题:筛质数
给定一个正整数 n,请你求出 1∼n中质数的个数。
输入格式
共一行,包含整数 n。
输出格式
共一行,包含一个整数,表示 1∼n 中质数的个数。
数据范围
1≤n≤10的6次方
输入样例:
8
输出样例:
4
#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 ++ )
{
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;
}
作者:yxc
链接:https://www.acwing.com/activity/content/code/content/49975/
来源:AcWing
约数
1.试除法求约数
什么是约数:如果一个数a除以另一个数b的余数为0,即 a%b == 0, 则b是a的约数。
如何求一个数x的所有约数:
用 x 除以 1 到 x 的所有数,如果余数是0,则把除数加到答案中。
可以优化吗?如果 a / b = c···0,则一定有 a % c = b····0。所以一个数 x 的约数肯定是成对存在的,对称轴是 根号x。
因此,只需要用 x 除以 1 到 根号x 之间的数,如果余数是0,则把除数以及x / 除数加到答案中。
作者:Hasity
链接:https://www.acwing.com/solution/content/148960/
来源:AcWing
例题:试除法求约数
给定 n 个正整数 ai,对于每个整数 ai,请你按照从小到大的顺序输出它的所有约数。
输入格式
第一行包含整数 n。
接下来 n 行,每行包含一个整数 ai。
输出格式
输出共 n行,其中第 i行输出第 i 个整数 ai 的所有约数。
数据范围
1≤n≤100,
1≤ai≤2×10的9次方
输入样例:
2
6
8
输出样例:
1 2 3 6
1 2 4 8
#include <iostream>
#include <vector> //vector存所有约数
#include <algorithm>
using namespace std;
int main()
{
int T;
cin >> T;
while(T--)
{
int n;
cin >> n;
vector<int> res; //存答案的数组
//因为约数成对出现,所以只需要循环到根号x
// 不要是用 i *i <= n,因为可能溢出
for(int i = 1; i <= n /i; i++)
{
if(n % i == 0)
{
res.push_back(i);
//如果i * i = x,添加i即可,不用添加 x / i
if(n / i != i) //有可能n是i的平方,避免重复
res.push_back(n / i);
}
}
sort(res.begin(), res.end()); //最后排序
for(auto x : res) cout << x << " ";
cout << endl;
}
} 时间复杂度nloga,一定要不断地算时间复杂度
2.约数个数
一个数的约数是由这个数的几个质因子相乘得到的。
例如:12 的质因子有 2,3。12的约数有:1,2,3,4,6,12。
约数1 是由 0 个 2, 0 个3相乘得到的。
约数2 是由 1 个 2, 0 个3相乘得到的。
约数3 是由 0 个 2, 1 个3相乘得到的。
约数4 是由 2 个 2, 0 个3相乘得到的。
约数6 是由 1 个 2, 1 个3相乘得到的。
约数12 是由 2 个 2, 1 个3相乘得到的。
12 可以分解为:2^2*3^1。所以2可以取 0 ~ 2个,3种取法。3可以取 0~1 个,2种取法。12的约数一共:2 * 3 = 6个。
也就是:把一个数N 写成:N = (p1^x1^)(p^x2)(p3^x3)…(pk^xk),其中pi为质数。则N的约数个数为:(x1+1)(x2+1)(x3+1)…(xk+1)
N=ans=d=pα11∗pα22∗⋯∗pαkk(α1+1)(α2+1)…(αk+1)
因为任何一个约数d可以表示成
pβ11∗pβ22∗⋯∗pβkk,0≤βi≤αi
每一项的βi如果不同,那么约数d就不相同(算数基本定理,每个数的因式分解是唯一的)
所以n的约数就跟βi的选法是一一对应的
β1一共有0∼α1种选法
β2一共有0∼α2种选法…
βk一共有0∼αk种选法乘法原理,
一共有ans个约数
例题:约数个数
给定 n个正整数 ai,请你输出这些数的乘积的约数个数,答案对 10的9次方+7取模。
输入格式
第一行包含整数 n。
接下来 n 行,每行包含一个整数 ai。
输出格式
输出一个整数,表示所给正整数的乘积的约数个数,答案需对 109+7109+7 取模。
数据范围
1≤n≤100,
1≤ai≤2×10的9次方
输入样例:
3
2
6
8
输出样例:
12
#include <iostream>
#include <algorithm>
#include <unordered_map>
using namespace std;
const int mod = 1e9+7 ;
int main()
{
int T;
cin >> T;
unordered_map<int, int> h;
while(T--)
{
int n; cin >> n;
//依次求出指数
for(int i = 2; i <= n / i; i++)
{
while(n % i == 0)
{
//指数+1
h[i] ++;
n = n / i;
}
}
//如果有剩余,也是一个质因子
if(n > 1) h[n]++;
}
long long res = 1;
for(auto iter = h.begin(); iter != h.end(); iter++)
{
//res = (x1+1)(x2+1)(x3+1)…(xk+1)
res = res * (iter->second + 1) % mod ;
}
cout << res;
}
作者:Hasity
链接:https://www.acwing.com/solution/content/148964/
来源:AcWing
3.约数之和
由算术基本定理得任意一个数A=pk11×pk22×…×pknn
则A的约数之和为(p01+p11+…+pk11)×(p02+p12+…+pk22)×…×(p0n+p1n+…+pknn)
,展开可以得到(k1+1)(k2+1)…(kn+1)个数之和,也就是A的约数之和。
下面的问题是如何求S(n)=x0+x1+x2+…+xn,S(n)=1+x×(x0+x1+…+xn−1)=1+S(n−1)×x
,边界情况S(0)=1,所以可以令t=1,乘以x再加上1得到S(1),再乘以x并加上1得到S(2),执行n次循环得到S(n)。
作者:yxc的小迷妹
链接:https://www.acwing.com/solution/content/145781/
来源:AcWing
例题:约数之和
给定 n 个正整数 ai,请你输出这些数的乘积的约数之和,答案对 10的9次方+7 取模。
输入格式
第一行包含整数 n。
接下来 n行,每行包含一个整数 ai。
输出格式
输出一个整数,表示所给正整数的乘积的约数之和,答案需对 10的9次方取模。
数据范围
1≤n≤100,
1≤ai≤2×10的9次方
输入样例:
3
2
6
8
输出样例:
252
#include <iostream>
#include <algorithm>
using namespace std;
const int mod = 1e9 + 7; //最后取模用的
int main(){
int n;
scanf("%d",&n);
unordered_map <int,int> primes; //存x这个数的所有约数
while(n --){ // 分解质因数并存在primes数组里
int x;
scanf("%d",&x);
for(int i = 2;i <= x / i;i ++){ //具体执行过程见解析①
while(x % i == 0){ //一直除到不能再整除为止
x /= i; //把x除掉i
primes[i] ++; //把第i个数的指数+1
}
}
if(x > 1) primes[x] ++; //如果x还是大于1的,说明x现在是原本x的因数,把x的指数+1
}
long long res = 1; //约数的和
// 这里其实就是按照约数之和的公式求出res
for(auto prime : primes){ //遍历一遍primes数组
int p = prime.first,a = prime.second; //p为底数,a为指数
long long t = 1; //见后面解析②
while(a --) t = (t * p + 1) % mod;
res = res * t % mod;
}
printf("%lld",res); //输出答案
return 0;
}
作者:冰中月
链接:https://www.acwing.com/solution/content/113135/
来源:AcWing
4.最大公约数(欧几里得算法,辗转相除法)
最大公约数(Greatest Common Divisor)指两个或多个整数共有约数中最大的一个。也称最大公因数、最大公因子,a, b的最大公约数记为(a,b),同样的,a,b,c的最大 公约数记为(a,b,c),多个 整数的最大公约数也有同样的记号。求最大公约数有多种 方法,常见的有 质因数分解法、 短除法、 辗转相除法、 更相减损法。
若d|a,d|b,则d|(ax + by)。有结论:(a,b) = (b,a mod b)。
证明:
amodb=a−⌊a/b⌋∗b=a−c∗b
(其中c为一整数)。则只需证明(a,b) = (b,a - c * b)即可。
对于左边任意一个公约数,由d|a,d|b,故d|a - c * b;
对于右边任意一个公约数,由d|a - c * b,又d|b,则有d|a - c * b + c * b,即d|a。
则左边的最大公约数 = 右边的最大公约数,故有(a,b) = (b,a mod b)。
int gcd(int a, int b)
{
return b ? gcd(b, a % b) : a;
}
例题:最大公约数
给定 n对正整数 ai,bi,请你求出每对数的最大公约数。
输入格式
第一行包含整数 n。
接下来 n 行,每行包含一个整数对 ai,bi。
输出格式
输出共 n行,每行输出一个整数对的最大公约数。
数据范围
1≤n≤10的5次方,
1≤ai,bi≤2×10的9次方
输入样例:
2
3 6
4 6
输出样例:
3
2
#include<bits/stdc++.h>
using namespace std;
/*
求两个正整数 a 和 b 的 最大公约数 d
则有 gcd(a,b) = gcd(b,a%b)
证明:
设a%b = a - k*b 其中k = a/b(向下取整)
若d是(a,b)的公约数 则知 d|a 且 d|b 则易知 d|a-k*b 故d也是(b,a%b) 的公约数
若d是(b,a%b)的公约数 则知 d|b 且 d|a-k*b 则 d|a-k*b+k*b = d|a 故而d同时整除a和b 所以d也是(a,b)的公约数
因此(a,b)的公约数集合和(b,a%b)的公约数集合相同 所以他们的最大公约数也相同 证毕#
*/
int gcd(int a, int b){
return b ? gcd(b,a%b):a;
}
int main(){
int n,a,b;
cin>>n;
while(n--) cin>>a>>b,cout<<gcd(a,b)<<endl;
return 0;
}
常算时间复杂度
end——————————————————————————————————————————