在大于等于1的整数中,只包含1和本身两个约数
枚举一个数所有可能的因数
优化:能够整数n的数都是成对出现,我们只要判断其中一个除数是否存在即可
模板:
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;
}
i
和n / i
是n的两个除数:若i
是n
的除数,那么n / i
也是n
的除数
只判断其中一个除数是否存在,所以只要保证i <= n / i
就能保证i为其中一个除数,这样就能把时间复杂度降到O(sqrt(n))
循环结束条件不写成:i * i <= n
是因为这样写可能导致溢出问题,当n
为整数最大值时,i * i
可能为负数
用i
枚举2~n-1
中的数,当n
能被i
整除时,说明i
是n
的因数,此时用i不断的分解n(n /= i),分解的次数表示n中有几个i
相乘,也就是i的指数
当n % i != 0
时停止,此时n
中不再包含因数i
以上的算法能够保证所有的因数都是质数,为什么?
质因数分解定理:每一个合数都能表示成质数的乘积
我们从最小的质数开始,不断地分解n,比如8被分解成3个2相乘,也能被分解为4 * 2
。2为质数,4 * 2
中的4能够表示成两个2相乘
所以就算一个正整数n的因子为合数,该因子也能被分解为质数
我们从最小的质数开始,枚举n
的所有可能因子,就能保证只有质数作为n
的因子。因为合数无法整数n
:此时的合数中含有n
中不含有的质因子,无法整数n
假设当前的除数为i
,那么n
已经被2~i-1
间的质数分解过了,即现在的n
中不包含任何2~i-1
中的质因子。若i
能整除n
,说明i中也不包含任何2~i-1
中的质因子,所以i
是一个质数
性质:1~n
中最多只包含一个大于等于sqrt(n)
的质因子,所以我们从2~sqrt(n)
枚举所有可能因数。但最后可能存在一个大于等于sqrt(n)
的质因子,所以需要特判一下
模板:打印n当中的质因子以及该质因子的指数
void divide(int n)
{
for (int i = 2; i <= n / i; ++ i )
{
if (n % i == 0)
{
int s = 0;
while (n % i == 0)
{
n /= i;
s ++ ;
}
printf("%d %d", i, s);
}
}
if (n > 1) printf("%d %d", n, 1);
}
埃氏筛法:
筛选n
中所有的质数,从2开始筛选,将n
中2的倍数(不包括2)删除,接着删除3的倍数,4的倍数,5,6…
能剩下的数就是质数,假设p
是剩下来的数,意味着2~p-1
中没有数能把p
筛掉,也就是没有数是p
的因子,所以p
是一个质数
如何用代码实现?
2~n
之间的所有数2~i - 1
的倍数,若i能剩下来,说明i是一个质数模板:
// st:该数是否为合数
// primes:存储1~n之间的质数
int st[N], primes[N], idx;
void get_primes(int n)
{
for (int i = 2; i <= n; ++ i )
{
if (!st[i]) primes[idx ++ ] = i;
for (int j = i + i; j <= n; j += i) st[j] = true;
}
}
时间复杂度为O(nlogn)
根据质因数分解定理,我们只需要在i为质数时,筛掉i的倍数(合数)即可
模板:
int st[N], primes[N], idx;
void get_primes(int n)
{
for (int i = 2; i <= n; ++ i )
{
if (!st[i])
{
primes[idx ++ ] = i;
for (int j = i + i; j <= n; j += i) st[j] = true;
}
}
}
质数定理:1~n当中有n / lnn个质数,lnn是以e为底n的对数
时间复杂度大概为O(n),真实的时间复杂度为O(nloglogn)
线性筛法:
实际运用中,大多使用线性筛法
n只会被最小质因子筛掉
用i
枚举2~n
中所有数,用j
从小到大枚举2~n
中已知的质数,每次把当前质数与i的乘积筛掉,因为这个乘积一定是合数
当前质数与i的关系,有两种情况:
i % primes[j] == 0
时,表示primes[j]
一定是i的最小质因子,同时一定是i * primes[j]
的最小质因子,筛除i * primes[j]
i % primes[j] != 0
时,表示primes[j]
一定小于i的最小质因子,也一定是i * primes[j]
的最小质因子,也筛除i * primes[j]
i * primes[j]
必定是一个合数,所以可以将其筛除i % primes[j] == 0
时,说明当primes[j]
是i
的最小质因子,由于线性筛法只用每个合数的最小质因子筛选,所以break
退出筛选每个数只有一个最小质因子,每个数只会被筛一次,所以它是线性的
模板:
int st[N], primes[N], idx;
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;
}
}
}
当n为 1 0 7 10^7 107时,线性筛法比埃氏筛法快一倍
问题:筛选质数的结束条件写primes[j] <= n / i
不会越界吗?
primes[j]
会在等于i的最小质因数的时候停下primes[j]
会在等于i时停下j
都不会超过cnt
,也就不会越界和判断一个数是否是质数类似,从小到大枚举所有的约数,能整除就是约数
因为约数成对出现,所以可以只判断其中一个约数是否存在
模板:
vector<int> get_divisors(int n)
{
vector<int> res;
for (int i = 1; i <= n / i; ++ i )
{
if (n % i == 0)
{
res.push_back(i);
if (i != n / i) res.push_back(n / i); // 若n为i的平方,防止约数的重复
}
}
sort(res.begin(), res.end());
return res;
}
约数个数:
一个正整数N,可以被分解成多个式子乘积的形式,每个式子是质数的幂运算。先求解N的质因数分解结果,再根据分解结果求其约数
每个式子中的质因数,它们都有自己的幂(指数),假设指数为 a i a_i ai
当其中一个质因数的指数在0~a
的范围中变化时,整个式子相乘的结果就是不同的,整个式子相乘能得到约数,所以得到的约数也是不同的
对于每个质因数的指数,它们能变化的范围是:0~ a i a_i ai,也就是( a i a_i ai + 1)种不同的选择
将每个质因数能选择的数量相乘,就能得到约数的数量
公式不好打,看y总板书即可
分解质因数用试除法,用哈希表保存每个质因数以及它们的指数,最后根据公式计算约数和即可
模板:
const int mod = 1e9 + 7;
unordered_map<int, int> primes;
for (int i = 2; i <= n / i; ++ i )
{
while (n % i == 0)
{
primes[i] ++ ;
n /= i;
}
}
if (n > 1) primes[n] ++ ;
long long res = 1;
for (auto prime : primes) res = res * (prime.second + 1) % mod;
printf("%d\n", res);
(int范围内的数据,约数数量最多的数的约数数量大概为1500个)
约数之和:
y总板书中列出的公式,将其展开,就能得到多个式子相乘再相加。这些乘积是所有约数中的一个,相加后就是所有约数之和
所以求约数之和的公式就是:将N分解质因数,对于每个质因数,根据其指数 a i a_i ai,从0~ a i a_i ai将所有的质数幂运算后相加,最后把相加的结果相乘即可
假设质数为p,其质数为a,从0~a将所有的指数幂运算后相加:
可以先long long t = 1
,每次只需要t = t * p + 1
,重复a次即可得到一个质因数所有指数幂运算结果,将res *= t即可
分解质因数与约数个数
中的做法一样
模板:
const int mod = 1e9 + 7;
unordered_map<int, int> primes;
for (int i = 2; i <= n / i; ++ i )
{
while (n / i == 0)
{
primes[i] ++ ;
n /= i;
}
}
if (n > 1) primes[n] ++ ;
long long res = 1;
for (auto prime : primes)
{
long long t = 1;
int p = prime.first, b = prime.second;
while (b -- ) t = (t * p + 1) % mod;
res = res * t % mod;
}
printf("%d\n", res);
d能整除a,也能整除b,那么d就能整除a的若干倍加上b的若干倍,即:
d | a && d | b -> d | ax + by
(a, b)
的最大公约数就等于(b, a % b)
的最大公约数,因为以下等式成立:
(a, b) = (b , a % b) == (a, b) = (b , a - c * b)
为什么以上等式成立?首先,模运算可以写成:a % b == a - [a / b] * b
,其中[]
表示下取整。那么[a / b]
就可以看成一个整数,a % b == a - c * b
根据一开始的结论,d | a && d | b -> d | ax + by
,当a = 1, b = -c
时,d | a % b
若一个约数既能整除a
,又能整除b
,那么该数就能整除a % b
等价于(a, b)
的公约数能整除a % b
,又因为(a, b)
的公约数能整除b
,所以对于(a, b)
的所有公约数,它们也是(b, a % b)
的公约数,即(b, a % b)
的公约数包含(a, b)
的公约数
对于(b, a - c * b)
的约数有:d | b && d | a - c * b -> d | b * x + (a - c * b) * y
,当x = c, y = 1
时,能推导出d | a
所以对于(b, a % b)
的所有公约数,它们也是(a, b)
公约数,即(b, a % b)
的公约数是(a, b)
公约数的一部分
对于(a, b)
和(b, a % b)
左边公约数为右边公约数的子集,右边公约数为左边公约数的子集,即左右两边的公约数相同
综上,(a, b)
的公约数和(b, a % b)
的公约数相同
欧几里得(辗转相除法)模板:
int gcd(int a, int b)
{
return b ? gcd(b, a % b) : a;
}
为什么该模板能求出最大公约数?
要使公约数最大,那么公约数一定是两数中较小的那个。即a % b == 0
或者b % a == 0
成立
根据推导出的等式,(a, b)
和(b, a % b)
的公约数相同,我们就能通过a % b
的运算,将两数不断变小,并试着取模。当a % b == 0
时,b
就是两者的最大公约数
因为这是第一对满足性质:公约数为两者中较小的数
(若b
大于a
,那么递归将使两数调换位置)
#include
using namespace std;
int n;
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 x;
scanf("%d", &n);
while (n -- )
{
scanf("%d", &x);
if (is_prime(x)) puts("Yes");
else puts("No");
}
return 0;
}
#include
using namespace std;
int n;
void divide(int n)
{
for (int i = 2; i <= n / i; ++ i )
{
if (n % i == 0)
{
int s = 0;
while (n % i == 0)
{
s ++ ;
n /= i;
}
printf("%d %d\n", i, s);
}
}
if (n > 1) printf("%d %d\n", n, 1);
printf("\n");
}
int main()
{
int x;
scanf("%d", &n);
while (n -- )
{
scanf("%d", &x);
divide(x);
}
return 0;
}
#include
using namespace std;
const int N = 1e6 + 10;
int n, cnt;
bool st[N];
void get_primes(int n)
{
for (int i = 2; i <= n; ++ i )
{
if (!st[i])
{
cnt ++ ;
for (int j = i + i; j <= n; j += i) st[j] = true;
}
}
}
int main()
{
scanf("%d", &n);
get_primes(n);
printf("%d\n", cnt);
return 0;
}
线性筛法:
#include
using namespace std;
const int N = 1e6 + 10;
bool st[N];
int primes[N], cnt;
int 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()
{
scanf("%d", &n);
get_primes(n);
printf("%d\n", cnt);
return 0;
}
#include
#include
#include
using namespace std;
int n;
vector<int> get_divisiors(int n)
{
vector<int> res;
for (int i = 1; i <= n / i; ++ i)
{
if (n % i == 0)
{
res.push_back(i);
if (i != n / i) res.push_back(n / i);
}
}
sort(res.begin(), res.end());
return res;
}
int main()
{
scanf("%d", &n);
int x;
while (n -- )
{
scanf("%d", &x);
auto res = get_divisiors(x);
for (auto x : res)
printf("%d ", x);
printf("\n");
}
return 0;
}
用set存储答案不用去重与排序:
#include
#include
#include
using namespace std;
int n;
set<int> get_divisiors(int n)
{
set<int> res;
for (int i = 1; i <= n / i; ++ i)
{
if (n % i == 0)
{
res.insert(i), res.insert(n / i);
}
}
return res;
}
int main()
{
scanf("%d", &n);
int x;
while (n -- )
{
scanf("%d", &x);
auto res = get_divisiors(x);
for (auto x : res)
printf("%d ", x);
printf("\n");
}
return 0;
}
题目要求n个数的乘积的约数个数,分解乘积的质因数与分解其约数的质因数得到的结果是相同的。所以我们可以分解每个因数的质因数
#include
#include
using namespace std;
const int mod = 1e9 + 7;
unordered_map<int, int> primes;
typedef long long LL;
int n;
int main()
{
scanf("%d", &n);
int x;
while (n -- )
{
scanf("%d", &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 prime : primes)
res = res * (1 + prime.second) % mod;
printf("%ld\n", res);
return 0;
}
#include
#include
using namespace std;
typedef long long LL;
const int mod = 1e9 + 7;
unordered_map<int, int> primes;
int n;
int main()
{
scanf("%d", &n);
int x;
while (n -- )
{
scanf("%d", &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 prime : primes)
{
LL t = 1;
LL p = prime.first, a = prime.second;
while (a -- ) t = (t * p + 1) % mod;
res = res * t % mod;
}
printf("%d\n", res);
return 0;
}
#include
using namespace std;
int n;
int gcd(int a, int b)
{
return b ? gcd(b, a % b) : a;
}
int main()
{
scanf("%d", &n);
int a, b;
while (n -- )
{
scanf("%d%d", &a, &b);
printf("%d\n", gcd(a, b));
}
return 0;
}