质数
模板:
// 试除法判断质数
bool is_prime(int x)
{
if (x < 2) return false;
//只需枚举一部分 使得 i<= x / i, 时间复杂度为√n
for (int i = 2; i <= x / i; i ++ )
if (x % i == 0)
return false;
return true;
}
// 试除法分解质因数
void divide(int n)
{
for (int i = 2; i <= n / i; i++)
{
if (n % i == 0)
{
int cnt = 0;
while (n % i == 0)
{
n /= i;
cnt++;
}
printf("%d %d\n", i, cnt);
}
}
//本身就是质数
if (n > 1) printf("%d %d\n", n, 1);
printf("\n");
}
//
试除法判定质数
代码:
#include<iostream>
using namespace std;
int n;
bool is_prime(int x)
{
if (x == 1) return false;
for (int i = 2; i <= x / i; i++)
{
if (x % i == 0) return false;
}
return true;
}
int main()
{
scanf("%d", &n);
while (n--)
{
int a;
scanf("%d", &a);
printf(is_prime(a) ? "Yes\n" : "No\n");
}
return 0;
}
分解质因数
代码:
#include<iostream>
using namespace std;
void divide(int n)
{
for (int i = 2; i <= n / i; i++)
{
if (n % i == 0)
{
int cnt = 0;
while (n % i == 0)
{
n /= i;
cnt++;
}
printf("%d %d\n", i, cnt);
}
}
//本身就是质数
if (n > 1) printf("%d %d\n", n, 1);
printf("\n");
}
int main()
{
int n;
scanf("%d", &n);
while (n--)
{
int a;
scanf("%d", &a);
divide(a);
}
return 0;
}
筛质数
思路:
筛法求质数,有两种方式:
- 埃氏筛法
- 线性筛法
埃氏筛法
埃氏筛法每次筛掉质数的倍数(大于等于2倍),这样最后剩下的就全是质数。 s t [ i ] st[i] st[i]为 f a l s e false false时,表示为质数。
埃氏筛法代码:
#include<iostream>
using namespace std;
const int N = 1000010;
bool st[N];
int primes[N];
int cnt;
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;
}
}
int main()
{
int n;
cin >> n;
get_primes(n);
cout << cnt << endl;
return 0;
}
线性筛法
线性筛法保证 n n 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
的最小质因子。
bool st[N];
int primes[N];
int cnt;
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;
}
}
}
线性筛法代码:
#include<iostream>
using namespace std;
const int N = 1000010;
bool st[N];
int primes[N];
int cnt;
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;
}
约数
试除法求约数
代码:
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
int n;
void get_divisors(int a)
{
vector<int> res;
for (int i = 1; i <= a / i; i++)
{
if (a % i == 0)
{
res.push_back(i);
// 防止平方根多次出现
if (i != a / i) res.push_back(a / i);
}
}
//从大到小排序
sort(res.begin(), res.end());
for (int t : res) printf("%d ", t);
printf("\n");
}
int main()
{
scanf("%d", &n);
while (n--)
{
int a;
scanf("%d", &a);
get_divisors(a);
}
return 0;
}
约数个数
思路:
给定一个数 N N N, p i p_i pi为 N N N的质约数,则可以表示为:
N = p 1 α 1 × p 2 α 2 × p 3 α 3 × . . . × p k α k N=p_{1}^{\alpha_1}\times p_{2}^{\alpha_2} \times p_{3}^{\alpha_3}\times... \times p_{k}^{\alpha_k} N=p1α1×p2α2×p3α3×...×pkαk
N N N的每一个约数 d i d_i di,可以表示为:
d i = p 1 β 1 × p 2 β 2 × p 3 β 3 × . . . × p k β k d_i=p_{1}^{\beta_1}\times p_{2}^{\beta_2} \times p_{3}^{\beta_3}\times... \times p_{k}^{\beta_k} di=p1β1×p2β2×p3β3×...×pkβk
其中, 0 ≤ β i ≤ α i 0 \le \beta_i \le \alpha_i 0≤βi≤αi, β i \beta_i βi的选法有 0 ∼ α i 0 \sim \alpha_i 0∼αi,共有 ( α i + 1 ) (\alpha_i + 1) (αi+1)种选法 ,根据排列组合原理,约数个数为:
( α 1 + 1 ) × ( α 2 + 1 ) × ( α 3 + 1 ) × . . . × ( α k + 1 ) (\alpha_1 + 1) \times (\alpha_2 + 1) \times (\alpha_3 + 1) \times ... \times (\alpha_k + 1) (α1+1)×(α2+1)×(α3+1)×...×(αk+1)
依次遍历 a i a_i ai,求 a i a_i ai的质因数以及每个质因数的个数,可以用哈希表保存结果
代码:
#include<iostream>
#include<unordered_map>
using namespace std;
const int 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]++;
}
long long res = 1;
for (auto prime : primes)
res = res * (prime.second + 1) % mod;
cout << res << endl;
return 0;
}
约数之和
思路:
根据约数个数的思路,约数之和为 d 1 + d 2 + d 3 + . . . + d k ① d_1 + d_2 + d_3 + ... + d_k \: \: \: ① d1+d2+d3+...+dk①
其中, d i = p 1 β 1 × p 2 β 2 × p 3 β 3 × . . . × p k β k ② d_i=p_{1}^{\beta_1}\times p_{2}^{\beta_2} \times p_{3}^{\beta_3}\times... \times p_{k}^{\beta_k} \: \: \: ② di=p1β1×p2β2×p3β3×...×pkβk②
约数之和为 ( p 1 0 + p 1 1 + p 1 2 . . . + p 1 α 1 ) . . . ( p k 0 + p k 1 + p k 2 . . . + p k α k ) (p_1^0+p_1^1+p_1^2...+p_1^{\alpha_1})...(p_k^0+p_k^1+p_k^2...+p_k^{\alpha_k}) (p10+p11+p12...+p1α1)...(pk0+pk1+pk2...+pkαk)
展开可得式①,展开的每一项均为 d i d_i di
( p i 0 + p i 1 + p i 2 . . . + p i α 1 ) (p_i^0+p_i^1+p_i^2...+p_i^{\alpha_1}) (pi0+pi1+pi2...+piα1)可以按照下面的方式进行推导:
t 0 = 1 t_0 = 1 t0=1
t 1 = t 0 × p + 1 = p + 1 t_1 = t_0 \times p + 1 = p + 1 t1=t0×p+1=p+1
t 2 = t 1 × p + 1 = p 2 + p + 1 t_2 = t_1 \times p + 1 = p^2 + p + 1 t2=t1×p+1=p2+p+1
…
t α i = t α i − 1 × p + 1 = p α i + p α i − 1 + . . . + p + 1 t_{\alpha_i }= t_{\alpha_i-1} \times p + 1 = p^{\alpha_i } + p^{\alpha_i-1} +... + p + 1 tαi=tαi−1×p+1=pαi+pαi−1+...+p+1,
最后计算 ∏ i = 1 n t α i \displaystyle\prod _{i=1}^n t_{\alpha_i} i=1∏ntαi,即为约数之和。
代码:
#include<iostream>
#include<unordered_map>
using namespace std;
const int 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]++;
}
long long res = 1;
for (auto prime : primes)
{
long long t = 1;
int p = prime.first, a = prime.second;
while (a--) t = (t * p + 1) % mod;
res = res * t % mod;
}
cout << res << endl;
return 0;
}
最大公因数
思路:
欧几里得算法,辗转相除法。
代码:
#include<iostream>
using namespace std;
int gcd(int m, int n)
{
return m % n == 0 ? n : gcd(n, m % n);
}
int main()
{
int n;
cin >> n;
while (n--)
{
int a, b;
cin >> a >> b;
cout << gcd(a, b) << endl;
}
return 0;
}
欧拉函数
欧拉函数
思路:
互质是公约数只有1的两个整数。在算数基本定理中, N = p 1 α 1 × p 2 α 2 × p 3 α 3 × . . . × p m α m N=p_{1}^{\alpha_1}\times p_{2}^{\alpha_2} \times p_{3}^{\alpha_3}\times... \times p_{m}^{\alpha_m} N=p1α1×p2α2×p3α3×...×pmαm,根据欧拉函数的定义: ϕ ( N ) = N × p 1 − 1 p 1 × p 2 − 1 p 2 × . . . × p m − 1 p m \phi(N) = N \times \cfrac{p_1 - 1}{p_1} \times \cfrac{p_2 - 1}{p_2} \times ... \times \cfrac{p_m - 1}{p_m} ϕ(N)=N×p1p1−1×p2p2−1×...×pmpm−1
只需求得 N N N的质因数 p i p_i pi即可,可以用到分解质因数的方法。
代码:
#include<iostream>
using namespace std;
int main()
{
int n;
cin >> n;
while (n--)
{
int x;
cin >> x;
int res = x;
for (int i = 2; i <= x / i; i++)
{
// 分解质因数
if (x % i == 0)
{
res = res / i * (i - 1);
while (x % i == 0) x /= i;
}
}
// 最后的质因数
if (x > 1) res = res / x * (x - 1);
cout << res << endl;
}
return 0;
}
筛法求欧拉函数
思路:
在用线性筛法求质因数的过程中,可以同时求出很多东西,比如某个数的欧拉函数。 1 ∼ N 1 \sim N 1∼N中与 N N N互质的数的个数即为 N N N的欧拉函数,记作 ϕ ( N ) \phi(N) ϕ(N)。
- 若 N N N为质数,则 ϕ ( N ) = N − 1 \phi(N) = N-1 ϕ(N)=N−1
- 若 N N N不为质数, N N N只会被最小的质因数给筛掉。
记 p j p_j pj为 N N N的质因子,枚举 2 ∼ N 2 \sim N 2∼N,记作 i i i。- 若 i m o d p j = 0 i \mod p_j = 0 imodpj=0,则 p j p_j pj为 p j × i p_j \times i pj×i的最小质因子,根据欧拉函数的计算公式: ϕ ( p j × i ) = ( p j × i ) × p 1 − 1 p 1 × p 2 − 1 p 2 × . . . × p k − 1 p k \phi(p_j \times i) = (p_j \times i) \times \cfrac{p_1 - 1}{p_1} \times \cfrac{p_2 - 1}{p_2} \times ... \times \cfrac{p_k - 1}{p_k} ϕ(pj×i)=(pj×i)×p1p1−1×p2p2−1×...×pkpk−1 p 1 ∼ p k p_1 \sim p_k p1∼pk均为 i i i的质因子, p j ∈ { p 1 , . . . , p k } p_j \in \{p_1,...,p_k\} pj∈{
p1,...,pk}。
观察 i × p 1 − 1 p 1 × p 2 − 1 p 2 × . . . × p k − 1 p k i \times\cfrac{p_1 - 1}{p_1} \times \cfrac{p_2 - 1}{p_2} \times ... \times \cfrac{p_k - 1}{p_k} i×p1p1−1×p2p2−1×...×pkpk−1,可以用 ϕ ( i ) \phi(i) ϕ(i)表示,则 ϕ ( p j × i ) = p j × ϕ ( i ) \phi(p_j \times i) = p_j \times \phi(i) ϕ(pj×i)=pj×ϕ(i) - i m o d p j ≠ 0 i \mod p_j \ne 0 imodpj=0, p j p_j pj小于 i i i的最小质因子,则 p j p_j pj是 p j × i p_j \times i pj×i的最小质因子
ϕ ( p j × i ) = ( p j × i ) × p 1 − 1 p 1 × . . . × p j − 1 p j × . . . × p k − 1 p k = ( i × p 1 − 1 p 1 × p 2 − 1 p 2 × . . . × p k − 1 p k ) × ( p j × p j − 1 p j ) = ϕ ( i ) × ( p j − 1 )
这样,便能遍历一次,求出 1 ∼ N 1 \sim N 1∼N内所有欧拉函数之和,这就是筛法求欧拉函数。
- 若 i m o d p j = 0 i \mod p_j = 0 imodpj=0,则 p j p_j pj为 p j × i p_j \times i pj×i的最小质因子,根据欧拉函数的计算公式: ϕ ( p j × i ) = ( p j × i ) × p 1 − 1 p 1 × p 2 − 1 p 2 × . . . × p k − 1 p k \phi(p_j \times i) = (p_j \times i) \times \cfrac{p_1 - 1}{p_1} \times \cfrac{p_2 - 1}{p_2} \times ... \times \cfrac{p_k - 1}{p_k} ϕ(pj×i)=(pj×i)×p1p1−1×p2p2−1×...×pkpk−1 p 1 ∼ p k p_1 \sim p_k p1∼pk均为 i i i的质因子, p j ∈ { p 1 , . . . , p k } p_j \in \{p_1,...,p_k\} pj∈{
p1,...,pk}。
欧拉定理,若 a a a与 n n n互质,则 a ϕ ( n ) ≡ 1 ( m o d n ) a^{\phi(n)} \equiv 1\:\:(mod \:\:n) aϕ(n)≡1(modn)
证明:
1 ∼ n 1 \sim n 1∼n中与 n n n互质的数为
a 1 , a 2 , a 3 . . . , a ϕ ( n ) a_1,a_2,a_3...,a_{\phi(n)} a1,a2,a3...,aϕ(n)
每个数 × a \times a ×a,得
a a 1 , a a 2 , a a 3 , . . . , a a ϕ ( n ) aa_1,aa_2,aa_3,...,aa_{\phi(n)} aa1,aa2,aa3,...,aaϕ(n)
每个数都与 n n n互质,在模 n n n的概念下,两组数可看作相同的,则有下式:
a 1 a 2 a 3 . . . a ϕ ( n ) ≡ a a 1 a a 2 a a 3 . . . a a ϕ ( n ) ( m o d n ) a ϕ ( n ) ≡ 1 ( m o d n )
特例: 费马定理
若 p p p为质数,则 a ϕ ( p ) ≡ 1 ( m o d p ) a^ {\phi(p)} \equiv 1 \:\:(mod \:\:p) aϕ(p)≡1(modp),即 a p − 1 ≡ 1 ( m o d p ) a^ {p-1} \equiv 1 \:\:(mod \:\:p) ap−1≡1(modp)
代码:
#include<iostream>
using namespace std;
typedef long long ll;
const int N = 1000010;
int primes[N], phi[N], cnt;
bool st[N];
ll get_eulers(int n)
{
ll res = 0;
phi[1] = 1;
for (int i = 2; i <= n; i++)
{
if (!st[i]) primes[cnt++] = i, phi[i] = i - 1;
for (int j = 0; primes[j] <= n / i; j++)
{
st[primes[j] * i] = true;
if (i % primes[j] == 0)
{
phi[primes[j] * i] = phi[i] * primes[j];
break;
}
phi[primes[j] * i] = phi[i] * (primes[j] - 1);
}
}
for (int i = 1; i <= n; i++) res += phi[i];
return res;
}
int main()
{
int n;
cin >> n;
cout << get_eulers(n) << endl;
return 0;
}
快速幂
快速幂
思路:
快速幂的思想是将指数 k k k看作二进制数之和这样便可以在 O ( l o g k ) O(logk) O(logk)的复杂度中求得结果。则推出下式
k = 2 i + . . . + 2 j + . . . + 2 log k a k = a 2 i . . . a 2 j . . . a 2 log k
又知
a 2 0 = a a 2 1 = a 2 0 × 2 = ( a 2 0 ) 2 . . . a 2 log k = a 2 ( log k ) − 1 × 2 = ( a 2 ( log k ) − 1 ) 2
若 k k k第 i i i位为 1 1 1,则结果 × a 2 i m o d p \times a^{2^i} \: mod \:p ×a2imodp,同时 k k k右移一位,底数平方得 a 2 i + 1 a^{2^{i+1}} a2i+1 , m o d p \: mod \:p modp,继续计算下一位。
代码
#include<iostream>
using namespace std;
typedef long long ll;
int q_pow(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;
}
int main()
{
int n;
scanf("%d", &n);
while (n--)
{
int a, b, p;
scanf("%d%d%d", &a, &b, &p);
printf("%d\n", q_pow(a, b, p));
}
return 0;
}
快速幂求逆元
思路:
若整数 b b b , m m m 互质,并且对于任意的整数 a a a,如果满足 b ∣ a b|a b∣a,则存在一个整数 x x x,使得 a / b ≡ a × x ( m o d m ) a/b \equiv a×x(mod \:m) a/b≡a×x(modm),则称 x x x 为 b b b 的模 m m m 乘法逆元,记为 b − 1 ( m o d m ) b^{-1}(mod \:m) b−1(modm)。
a / b ≡ a × x ( m o d m ) a / b × b ≡ a × b × x ( m o d m ) b × x ≡ 1 ( m o d m )
又根据费马定理, b m − 1 ≡ 1 ( m o d m ) b^ {m-1} \equiv 1 \:\:(mod \:\:m) bm−1≡1(modm),则 b × x = b m − 1 b \times x = b^ {m-1} b×x=bm−1, x = b m − 2 x = b^ {m-2} x=bm−2,若整数 b b b , m m m 互质,则存在 b b b的乘法逆元。
代码
#include<iostream>
using namespace std;
typedef long long ll;
int q_pow(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;
}
int gcd(int a, int b)
{
return a % b ? gcd(b, a % b) : b;
// return b ? gcd(b, a % b) : a;
}
int main()
{
int n;
scanf("%d", &n);
while (n--)
{
int a, p;
scanf("%d%d", &a, &p);
int res = q_pow(a, p - 2, p);
// 最大公因数为1,互质
// if (a % p) 也可,
if (gcd(a, p) == 1)printf("%d\n", res);
else puts("impossible");
}
return 0;
}
扩展欧几里得算法
扩展欧几里得算法
思路:
裴蜀定理: 对任何整数 a a a、 b b b,存在整数 x x x、 y y y,使得 a x + b y = ( a , b ) ax + by = (a, b) ax+by=(a,b), ( a , b ) (a, b) (a,b)表示 a a a、 b b b的最大公因数,令 d = ( a , b ) d=(a, b) d=(a,b)。
若 b = 0 b=0 b=0,则 d = a d=a d=a, x = 1 x=1 x=1, y = 0 y=0 y=0
由数学定理知, ( a , b ) = ( b , a m o d b ) (a, b) = (b, a \: mod \: b) (a,b)=(b,amodb)。 a a a、 b b b交换顺序后,则有
( a m o d b ) x + b y = d ( a m o d b ) = a − ⌊ a b ⌋ b ( a − ⌊ a b ⌋ b ) x + b y = d a x + ( y − ⌊ a b ⌋ x ) b = d (a \: mod \: b)x+by=d \\ (a \: mod \: b) = a - \lfloor \cfrac{a}{b} \rfloor b \\ (a - \lfloor \cfrac{a}{b} \rfloor b)x+by = d \\ ax + (y - \lfloor \cfrac{a}{b} \rfloor x)b = d (amodb)x+by=d(amodb)=a−⌊ba⌋b(a−⌊ba⌋b)x+by=dax+(y−⌊ba⌋x)b=d
根据公式看到,与 b b b位置相同的参数的系数对应的为 y y y。
代码:
#include<iostream>
using namespace std;
/**
int gcd(int a, int b)
{
return b ? gcd(b, a % b) : a;
}
*/
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 main()
{
int n;
scanf("%d", &n);
while (n--)
{
int a, b, x, y;
scanf("%d%d", &a, &b);
exgcd(a, b, x, y);
printf("%d %d\n", x, y);
}
return 0;
}
线性同余方程
思路:
∵ a x ≡ b ( m o d m ) , ∃ y ∈ Z \because a x \equiv b(\mod m),\exist y \in Z ∵ax≡b(modm),∃y∈Z,使得: a x − m y = b ax-my=b ax−my=b。令 y = − y y=-y y=−y,则 a x + m y = b ax+my=b ax+my=b,令 d = ( a , m ) d=(a,m) d=(a,m),并且 d ∣ b d|b d∣b。
用拓展欧几里得算法求解, a x + m y = d ax+my=d ax+my=d, x x x、 y y y扩大 b d \cfrac{b}{d} db 倍,即可化为 a x + m y = b ax+my=b ax+my=b。
输出答案必须在 i n t int int 范围之内,计算出来的 x x x可能超过限制,又
a x m o d m = ( a ∗ ( x m o d m ) ) m o d m ax \mod m = (a * (x \mod m)) \mod m axmodm=(a∗(xmodm))modm
保险起见,最后的 x m o d m x \mod m xmodm。
代码:
#include<iostream>
using namespace std;
/**
int gcd(int a, int b)
{
return b ? gcd(b, a % b) : a;
}
*/
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 main()
{
int n;
scanf("%d", &n);
while (n--)
{
int a, b, m, x, y;
scanf("%d%d%d", &a, &b, &m);
int d = exgcd(a, m, x, y);
if (b % d) puts("impossible");
else printf("%d\n", (long long)b / d * x % m);
}
return 0;
}
转载:https://blog.csdn.net/weixin_51624761/article/details/128532782