常见数论
算法基础系列
算术基本定理
公理:任何一个大于1的自然数 N,如果 N 不为质数,那么 N 可以唯一分解成有限个质数的乘积
公式: N = P 1 d 1 + P 2 d 2 + + P n d n , d > 0 N=P_1{d_1}+P_2{d_2}+cdots+P_n^{d_n},d>0 N=P1d1+P2d2++Pndn,d>0
int n;
cin >> n;
unordered_map<int, int> primes;
//用map存,用auto来写
//另一个方法见下文线性筛
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] ++ ;
}
多重集的排列数问题
质数问题
判断质数
方法:试除法
时间复杂度: O ( n ) O(sqrt n) O(n )(一定是)
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;
}
分解质因数
方法:试除法
时间复杂度:最坏是 O ( log n ) O(log_{}{n}) O(logn),最好是 O ( n ) O(sqrt n) O(n )
优化:从 O ( n ) O(n) O(n)降低到 O ( n ) O(sqrt n) O(n )
原理: n n n 当中最多只包含一个大于 n sqrt n n 的质因子
模板
void divide(int x)
{
for (int i = 2; i <= x / i; i++)
{
if (x % i == 0)
{
int s = 0;
while (x % i == 0)
x /= i, s++;
cout << i << ' ' << s << endl;
}
}
if (x > 1)
cout << x << ' ' << 1 << endl;
puts("");
}
筛法
原理:在 2 ∽ n 2acksim n 2∽n中,从 2 开始,删掉每一个质数的倍数,最后剩下的即使质数
最常用:线性筛
朴素筛——枚举每一个数
int prime[N],cnt;
bool st[N];
void get_prime(int x)
{
for (int i = 2; i <= x; i++)
{
if(!st[i])
prime[cnt++] = x;
for (int j = i + i; j <= x; j += i)
st[j] = true;
}
}
埃氏筛法
时间复杂度: O ( n log log n ) O(nlog_{}{log_{}{n}}) O(nloglogn)
int prime[N], cnt;
bool st[N];
void get_prime(int x)
{
for (int i = 2; i <= x; i++)
{
if (!st[i])
{
prime[cnt++] = x;
for (int j = i + i; j <= x; j += i)
st[j] = true;
}
}
}
线性筛法
时间复杂度: O ( n ) O(n) O(n)
该方法能在在 O ( n ) O(n) O(n) 内,求出 1 ∽ n 1acksim n 1∽n 之间有所有质数以及每个数的最小质因子
int prime[N], cnt;
bool st[N];
void get_prime(int x)
{
for (int i = 2; i <= x; i++)
{
if (!st[i])
prime[cnt++] = i;
for (int j = 0; prime[j] <= x / i; j ++)
{
st[prime[j] * i] = true;
if(i % prime[j] == 0 )
break;//prime[j] 一定是i的最小质因子
}
}
}
注意:当数据量是106 时,埃氏筛法和线性筛法时间差不多
当数据量为107 时,线性筛法速度是埃氏筛法的两倍
约数问题
求所有约数
方法:试除法
模板代码
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);
}
sort(res.begin(), res.end());
return res;
}
约数个数
算术基本定理公式: N = P 1 d 1 + P 2 d 2 + + P n d n , d > 0 N=P_1{d_1}+P_2{d_2}+cdots+P_n^{d_n},d>0 N=P1d1+P2d2++Pndn,d>0
源自算术基本定理
约数个数公式: S = ( d 1 + 1 ) + ( d 2 + 1 ) + + ( d n + 1 ) S=(d_1+1)+(d_2+1)+cdots+(d_n+1) S=(d1+1)+(d2+1)++(dn+1)
约数之和
算术基本定理公式: N = P 1 d 1 + P 2 d 2 + + P n d n , d > 0 N=P_1{d_1}+P_2{d_2}+cdots+P_n^{d_n},d>0 N=P1d1+P2d2++Pndn,d>0
约数之和公式: S = ( 1 + P 1 0 + P 1 1 + + P 1 d 1 ) ( 1 + P 2 0 + + P 2 d 2 ) ( 1 + P k 0 + + P k d k ) S=(1+P_10+P_11+cdots+P_1{d_1})(1+P_20+cdots+P_2{d_2})cdots(1+P_k0+cdots+P_k^{d_k}) S=(1+P10+P11++P1d1)(1+P20++P2d2)(1+Pk0++Pkdk)
约数个数和约数之后的共同模板
#include <bits/stdc++.h>
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 prime : primes)
{
LL p = prime.first, d = prime.second;
LL t = 1;
while(d--)
t = (t * p + 1) % mod;
res = res * t % mod;
}
//约数个数
LL res = 1;
for (auto prime : primes)
res = res * (prime.second + 1) % mod;
cout << res << endl;
return 0;
}
最大公约数gcd
时间复杂度: O ( log n ) O(log_{n}) O(logn)
欧几里得算法 – 辗转相除法 gcd(a,b)
(a,b)
的最大公约数 = = = (b,a mod b)
理论上最多转换
模板
int gcd(int a,int b)
{
return b ? gcd(b,a mod b) : a;
}