质数
1、质数的判定:
试除法判定质数
//朴素写法,时间复杂度O(n)
bool isprime(int n){
if(n<2) return false;
for(int i = 2; i < n; ++ i)
if(n % i == 0)
return false;
return true;
}
我们发现对于任何一个能整除n的数d,则n / d也能整除n
所以我们只需要枚举到n / d即可
//优化后代码,时间复杂度是O(根号n)
bool isprime(int n){
if(n<2) return false;
for(int i = 2; i <= n / i; ++ i)
if(n % i == 0)
return false;
return true;
}
2、分解质因数
//试除法分解质因数
void divide(int n){
for(int i = 2; i <= n; ++ i)
if(n % i == 0){
int s = 0;
while(n % i == 0){
n /= i;
s ++;
}
cout << i << ' ' << s << endl;
}
puts("");
}
我们发现n中至多有一个质因数大于根号n,假设有两个质因数x、y大于根号n,那么x * y就大于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 ++;
}
cout << i << ' ' << s << endl;
}
if(n > 1) cout << n << ' ' << 1 << endl;
puts("");
}
线性筛
筛法求质数
朴素筛法:从小到大枚举,未被标记则加入质数表内,然后把它的倍数筛除
//时间复杂度O(nlogn)
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[j] = true;
}
}
我们发现有很多冗余,有些数被筛除了很多次,观察质数和约数的性质,我们发现每一个约数都是质数的倍数,所以我们只用筛除质数的倍数即可
//时间复杂度为O(nloglogn)
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[j] = true;
}
}
}
线性筛法:每次只用一个数的最小质因数将这个数筛除
//时间复杂度O(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;
}
}
}
线性筛约数个数
线性筛约数之和
约数
试除法求约数
和试除法判定质数相同,我们发现对于任何一个能整除n的数d,则n / d也能整除n,所以我们只用枚举到n / d,用d算出来另一个约数n / d即可
//时间复杂度O(根号n + nlogn),排序时间复杂度O(nlogn)
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);
}
sort(res.begin(), res.end());
return res;
}
约数个数与约数之和
最大公约数
欧几里德算法
int gcd(int a, int b)
{
return b ? gcd(b, a%b) : a;
}
欧拉函数
欧拉函数及证明
欧拉函数的七个性质
筛法求欧拉函数
具体代码如下
#include<iostream>
using namespace std;
typedef long long LL;
const int N = 1000100;
int n;
int primes[N], cnt;
int phi[N];
bool st[N];
LL get_euler(int n){
phi[1] = 1;
for(int i = 2; i <= n; ++ i){
if(!st[i]){
primes[cnt ++] = i;
phi[i] = i - 1;// i是质数时phi[i] = i - 1
}
for(int j = 0; primes[j] <= n / i; ++ j){
st[i * primes[j]] = true;
if(i % primes[j] == 0){
phi[primes[j] * i] = primes[j] * phi[i];
//i % pj == 0时, phi[pj * i] = pj * phi[i]
break;
}
phi[primes[j] * i] = (primes[j] - 1) * phi[i];
//i % pj != 0时, phi[pj * i] = (pj - 1) * phi[i]
}
}
LL res = 0;
for(int i = 1; i <= n; ++ i) res += phi[i];
return res;
}
int main(){
cin >> n;
cout << get_euler(n) << endl;
return 0;
}
欧拉定理及其证明
费马小定理
快速幂
思想:倍增的思想,和二进制可以组成任何数
代码模板
int qmi(int a, int b, int p){
int res = 1;
while(b){
if(b & 1) res = (LL)res * a % p;
b >>= 1;
a = (LL)a * a % p;
}
return res;
}
乘法逆元的定义
若整数 b,m 互质,并且对于任意的整数 a,如果满足 b|a,则存在一个整数 x,使得 a/b≡a×x(mod m),则称 x 为 b 的模 m 乘法逆元,记为 b^−1 (mod m)。
b 存在乘法逆元的充要条件是 b 与模数 m 互质。当模数 m 为质数时,b^m−2 即为 b 的乘法逆元。
扩展欧几里德算法
裴蜀定理:对于任意正整数a,b,那么一定存在非零整数x、y使得ax + by = gcd(a, b)
利用扩展欧几里得算法我们可以求出一组通解
x
0
,
y
0
x_0,y_0
x0,y0
其通解公式为:
{
x
k
=
x
0
+
k
×
b
g
c
d
(
a
,
b
)
y
k
=
y
0
−
k
×
a
g
c
d
(
a
,
b
)
k
∈
Z
\left\{\begin{matrix} x_k=x_0+k\times \frac{b}{gcd(a,b)} & \\ y_k=y_0-k\times \frac{a}{gcd(a,b)}& \end{matrix}\right. k\in \mathbb{Z}
{xk=x0+k×gcd(a,b)byk=y0−k×gcd(a,b)ak∈Z
代码模板
int exgcd(int a, int b, int &x, int &y){
if(!b){
x = 1, y = 0;
return a;
}
int d = exgcd(b, a % b, y, x);
y -= a/b * x;
return d;
}
中国剩余定理
高斯消元
代码模板
int gauss(){
int r, c;
for(c = 0, r = 0; c < n; ++ c){
int t = r;
for(int i = r + 1; i < n; ++ i)
if(fabs(a[i][c]) > fabs(a[t][c]))
t = i;
if(fabs(a[t][c]) < eps) continue;
for(int i = c; i < n + 1; ++ i) swap(a[t][i], a[r][i]);
for(int i = n; i >= c; -- i) a[r][i] /= a[r][c];
for(int i = r + 1; i < n; ++ i)
if(fabs(a[i][c]) > eps)
for(int j = n; j >= c; -- j)
a[i][j] -= a[r][j] * a[i][c];
r ++;
}
if(r < n){
for(int i = r; i < n; ++ i)
if(fabs(a[i][n]) > eps)
return 2;
return 1;
}
for(int i = n - 1; i >= 0; -- i)
for(int j = i + 1; j < n; ++ j)
a[i][n] -= a[i][j] * a[j][n];
return 0;
}
求组合数