算法基础系列
算术基本定理
公理:任何一个大于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 2\backsim 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(n\log_{}{\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
1\backsim 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_1^0+P_1^1+\cdots+P_1^{d_1})(1+P_2^0+\cdots+P_2^{d_2})\cdots(1+P_k^0+\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 % b) : a;
}