文章目录
0x00 整除
重点不多,主要集中在几点上:
1.整除符号:
a
∣
b
a | b
a∣b
表示,b可以被a整除。
记忆方法:b >= a(像分母一样)
2. a | b 且 b | c,则a | (b * x + c * y)
3. a * x + b + y = 1 且 a | n , b | n,则 (a + b) | n
4. b = 任何整数 * d + c,则d | b 的充要条件为d | c
5. 2 能整除 a 的末 1 位,则 2 | a
4 能整除 a 的末 2 位,则 4 | a
8 能整除 a 的末 3 位,则 8 | a
3 能整除 a 的各位数字和,则 3 | a
9 能整除 a 的各位数字和,则 9 | a
11 能整除 a 的偶位数字和与奇位数字之和的差,则 11 | a
7或11或13整除a的末三位数字所组成的数与末三位以前的数字所组成的数的差(大数减小数),则7或11或13 | a
0x10 同余
a
≡
b
(
m
o
d
m
)
a\equiv b\pmod{m}
a≡b(modm)
表示 a 与 b 关于 m 同余
若
a
≡
b
(
m
o
d
m
)
a\equiv b\pmod{m}
a≡b(modm) 且
b
≡
c
(
m
o
d
m
)
b\equiv c\pmod{m}
b≡c(modm)
则
a
≡
c
(
m
o
d
m
)
a\equiv c\pmod{m}
a≡c(modm)
若
a
≡
b
(
m
o
d
m
)
a\equiv b\pmod{m}
a≡b(modm) 则
a
∗
c
≡
b
∗
c
(
m
o
d
m
)
a*c\equiv b*c\pmod{m}
a∗c≡b∗c(modm)
若
a
≡
b
(
m
o
d
m
)
a\equiv b\pmod{m}
a≡b(modm) ,
c
≡
d
(
m
o
d
m
)
c\equiv d\pmod{m}
c≡d(modm) 则
a
∗
c
≡
b
∗
d
(
m
o
d
m
)
a*c\equiv b*d\pmod{m}
a∗c≡b∗d(modm)
若 a ≡ b ( m o d m ) a\equiv b\pmod{m} a≡b(modm) 则 a n ≡ b n ( m o d m ) a^n\equiv b^n\pmod{m} an≡bn(modm)
a
∗
b
(
m
o
d
m
)
a* b\pmod{m}
a∗b(modm) ==
(
a
∗
m
)
∗
(
b
∗
m
)
(
m
o
d
m
)
(a * m) * (b * m) \pmod{m}
(a∗m)∗(b∗m)(modm)
a
m
o
d
p
=
x
a \mod p = x
amodp=x 且
a
m
o
d
q
=
x
a\mod q = x
amodq=x 则 p , q 互质
本质: a ≡ b ( m o d m ) a\equiv b\pmod{m} a≡b(modm) => a - b = m * k
0x20 最大公约数
0x21 辗转相除法(欧几里得法)
inline int gcd(int x , int y){
return y ? gcd(y , x % y) : x ;
}
0x22 最小公倍数
gcd(a , b) * lcm(a , b) = a * b ;
逆推一下即可。
0x23 拓展欧几里得定理
目的: 已知 a , b , 求一组 p , q 使得
p
∗
a
+
q
∗
b
=
g
c
d
(
a
,
b
)
p * a+q*b=gcd(a , b)
p∗a+q∗b=gcd(a,b)
#include<iostream>
#include<cstdio>
using namespace std ;
int num1 ,num2 ;
int fir ,sec ;
inline int extended_gcd(int a , int b){
int rep ,tmp ;
if(b == 0){
fir = 1 ;
sec = 0 ;
return a ;
}
rep = extended_gcd(b , a % b) ;
tmp = fir ;
fir = sec ;
sec = tmp - a / b * sec ;
return rep ;
}
int main(){
scanf("%d%d" , &num1 , &num2) ;
int ans = extended_gcd(num1 , num2) ;
printf("%d * %d + %d * %d = %d" , num1 , fir , num2 , sec , ans) ;
return 0 ;
}
0x24 求解线性同余方程
首先我们要明确一件事情:针对
a
∗
x
+
b
∗
y
=
c
a*x+b*y=c
a∗x+b∗y=c
等价于
a
∗
x
≡
c
(
m
o
d
b
)
a*x\equiv c\pmod{b}
a∗x≡c(modb)
充要条件:
c
m
o
d
g
c
d
(
a
,
b
)
=
0
c \mod{gcd(a , b)} = 0
cmodgcd(a,b)=0
解法:
- 首先,找到一组 a * x0 + b * y0 = gcd(x0 , y0)
- 变式:
a ∗ x 0 ∗ c g c d ( x 0 , y 0 ) + b ∗ y 0 ∗ c g c d ( x 0 , y 0 ) = c \frac{a * x_0 * c}{gcd(x_0 , y_0)} + \frac{b * y_0 * c}{gcd(x_0 , y_0)} = c gcd(x0,y0)a∗x0∗c+gcd(x0,y0)b∗y0∗c=c
此时
a ∗ x 0 ∗ c g c d ( x 0 , y 0 ) + b ∗ y 0 ∗ c g c d ( x 0 , y 0 ) = c a*\frac{x_0 * c}{gcd(x_0 , y_0)}+b* \frac{ y_0 * c}{gcd(x_0 , y_0)}=c a∗gcd(x0,y0)x0∗c+b∗gcd(x0,y0)y0∗c=c
a和b的两个系数分别是x , y的解。
若 gcd(a , b) = 1 , 且x0 , y0 为 a x + b y = c的 1 组解,则
{
x
=
x
0
+
b
∗
t
y
=
y
0
−
a
∗
t
\begin{cases}x = x_0 + b*t\\y=y_0-a*t\end{cases}
{x=x0+b∗ty=y0−a∗t
目的:求最小整数解
t
=
b
gcd
(
a
,
b
)
t=\frac{b}{\gcd(a ,b)}
t=gcd(a,b)b
记得
x
=
(
x
m
o
d
t
+
t
)
m
o
d
t
x = (x \mod t + t)\mod{t}
x=(xmodt+t)modt
(为了防负数)
0x30 逆元
“除以一个数再取模等同于乘以这个数的逆元再取模 ”
这就是逆元的本质。具体的 code:
#include<iostream>
#include<cstdio>
using namespace std ;
int a ;
const int MOD = 1000007 ;
//拓展欧几里得求逆元
//时间复杂度:O(logn)
//特点:只要存在逆元即可求,适用于个数不多但是mod很大的时候,也是最常见的一种求逆元的方法
int fir ;
int sec ;
inline int exgcd(int a , int b){
if(b == 0){
fir = 1 ;
sec = 0 ;
return a ;
}
int ans = exgcd(b , a%b) ;
int cmp = fir ;
fir = sec ;
sec = cmp - a / b * sec ;
return ans ;
}
inline int inv_gcd(int a , int mod){//求a在mod下的逆元(不存在返回-1)
int d = exgcd(a , mod) ;
return d == 1 ? (fir % mod + mod) % mod : -1 ;
}
//费马小定理、欧拉定理求逆元(蒙哥马利快速幂求逆元)
//时间复杂度:O(logmod)
//一般在mod是个素数的时候用,比扩欧快一点。但是如果是合数,相信一般没人无聊到去算个欧拉函数。
inline int qpow(int base , int p , int mod){
int res = 1 ;
while(p){
if(p & 1)
res = (res % mod) * base % mod ;
base = (base % mod) * base % mod ;
p >>= 1 ;
}
return res ;
}
inline int inv_fermat(int a , int mod){
return qpow(a , mod-2 , mod) ;
}
//线性求逆元
//时间复杂度:O(n)
//调用前要先预处理、调用的时候要先对除数取mod;mod数是不大的素数而且多次调用,比如卢卡斯定理。
int invnum[MOD + 5] ;
inline void inv(int mod){
invnum[1] = 1 ;
for(int i = 2;i < mod;++i)//注意开long long
invnum[i] = (mod - mod / i) * invnum[mod % i] % mod ;
}
//递归求逆元
//时间复杂度:O(logmod)
//好像找到了最简单的算法了!!
//适用范围: mod数是素数,所以并不好用,比如中国剩余定理中就不好使
//因为很多时候可能会忘记考虑mod数是不是素数。
inline int inv_dp(int i , int mod){
if(i == 1)
return 1 ;
return (mod - mod / i) * inv_dp(mod % i , mod) % mod ;
}
int main(){
int mod ;
scanf("%d%d" , &a , &mod) ;
int ans1 = inv_gcd(a , mod) ;
cout << ans1 << '\n' ;
int ans2 = inv_fermat(a , mod) ;
cout << ans2 << '\n' ;
inv(mod) ;
cout << invnum[a] << '\n' ;
int ans4 = inv_dp(a , mod) ;
cout << ans4 << '\n' ;
return 0 ;
}
0x40 中国剩余定理
解决问题:线性同余方程组
将下面的方程组进行分解:
基于每一项提取系数并拆分得出 k 个方程组:
这些方程组都要在乘上一个系数才是真正的答案。
code:
#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std ;
const int MAXN = 20 ;
long long n ;
long long mod[MAXN] ;
long long num[MAXN] ;
inline long long exgcd(long long a , long long b , long long &x , long long &y){
if(b == 0){
x = 1 ;
y = 0 ;
return a ;
}
long long gcd = exgcd(b , a%b , x , y) ;
long long t = x ;
x = y ;
y = t - a / b * y ;
return gcd ;
}
inline long long CRT(){
long long x ,y ;
long long ans = 0 ;
long long sum = 1 ;
long long m = 0 ;
for(long long i = 1;i <= n;++i){
sum *= mod[i] ;
}
for(long long i = 1;i <= n;++i){
m = sum / mod[i] ;//拆分每个方程组,m是拆下来后剩下的其他值
exgcd(mod[i] , m , x , y) ;//求解单个线性方程组
ans = (ans + y * m * num[i]) % sum ;
}
return ans > 0 ? ans : ans + sum ;//负数取模
}
int main(){
scanf("%lld" , &n) ;
for(long long i = 1;i <= n;++i){
scanf("%lld%lld" , mod+i , num+i) ;
}
printf("%lld" , CRT()) ;
return 0 ;
}
0x50 斐波那契数
斐波那契通项公式:
其他的部分会单独一篇blog详细解读。
0x60 卡特兰数
卡特兰数基本通项公式:
f
n
=
1
n
+
1
C
2
n
n
f_n =\frac{1}{n+1}C_{2n}^n
fn=n+11C2nn
为什么是基本公式?因为不同的题会对卡特兰数的计算产生不同的影响,虽然不同但不会产生大的变化。
基本特征:求方案数、对方案数产生一定限制。
具体例子会单独出一篇 blog 解释。
0x70 欧拉函数
0x71 欧拉函数的定义
定义:欧拉函数φ(n)的值等于序列0,1,2,…,n-1中与n互素的数的个数。
友情提示:
φ
(
p
h
i
ˋ
)
φ(ph\grave i)
φ(phiˋ)
特别的,φ(1) = 1(和1互质的数(小于等于1)就是1本身)。
性质:
- 欧拉函数 φ(n * m) = φ(n) * φ(m)
(判定条件:n、m 互质) - 对于 质数 p,它的 欧拉函数φ(p)= p - 1
- 对于 奇数 n,φ(2 * n)= φ(n)
- 当 n = pk 时 ,φ(n)= pk - pk-1
- n 中 与 n 互质的数的和为 φ ( n ) 2 ∗ n \frac{φ(n)}{2 * n} 2∗nφ(n)(n > 1)
- φ ( n ) ( n > 2 ) φ(n)(n > 2) φ(n)(n>2)的值为偶数
- 若 p ∣ n 且 p 2 ∣ n ,则 φ ( n ) = φ ( n p ) ∗ p p\mid n且p^2\mid n,则 φ(n)=φ(\frac{n}{p})*p p∣n且p2∣n,则φ(n)=φ(pn)∗p
- 若 p ∣ n 且 p 2 ∤ n ,则 φ ( n ) = φ ( n p ) ∗ ( p − 1 ) p\mid n且p^2\nmid n,则 φ(n)=φ(\frac{n}{p})*(p-1) p∣n且p2∤n,则φ(n)=φ(pn)∗(p−1)
0x72 欧拉函数的线性筛法
该算法在可在线性时间内筛素数的同时求出所有数的欧拉函数。
需要用到如下性质(p为质数):
-
φ ( p ) = p - 1 因为质数p除了1以外的因数只有p,故1至p的整数只有p与p不互质
-
如果i mod p = 0, 那么 φ(i * p) = p * φ(i)
-
若i mod p ≠0, 那么phi(i * p)=phi(i) * (p-1)
[i mod p 不为0且p为质数, 所以i与p互质, 那么根据欧拉函数的积性phi(i * p)=phi(i) * phi( p ) 其中phi( p )=p-1即第一条性质]
#include<iostream>
#include<cstdio>
using namespace std ;
const int MAXN = 1e6 ;
int phi[MAXN] ;
int prime[MAXN] ;
int tot ;
int ans = 0 ;
bool mark[MAXN] ;
int n ;
inline void get_phi(){
phi[1] = 1 ;
for(int i = 2;i <= n;++i){
if(mark[i] == false){
prime[++tot] = i ;
phi[i] = i - 1 ;
}
for(int j = 1;j <= tot;++j){
if(i * prime[j] > MAXN)
break ;
mark[i * prime[j]] = true ;
if(i % prime[j] == 0){
phi[i * prime[j]] = phi[i] * prime[j] ;
break ;
}else
phi[i * prime[j]] = phi[i] * (prime[j] - 1) ;
}
}
}
int main(){
scanf("%d" , &n) ;
get_phi() ;
for(int i = 1;i <= n;++i){
if(i % 11 == 0)
printf("\n") ;
printf("%d " , phi[i]) ;
}
return 0 ;
}
0x73 欧拉函数的使用
RSA算法
自己看吧考试又不考:RSA算法详解
欧拉函数对其他算法的优化
利用欧拉函数的积性函数 特性进行优化。
*注:积性函数:积性函数指对于所有 互质 的整数a和b有性质 f (ab) = f (a) * f (b)的 数论函数 。
证明:
∑
d
∣
n
φ
(
d
)
=
n
\sum\limits_{d\mid n}φ (d)=n
d∣n∑φ(d)=n
参考资料:欧拉函数
欧拉函数的性质及证明
0x80 素数
0x81 威尔逊定理
若 p 为素数,则:
(
p
−
1
)
!
≡
−
1
(
m
o
d
p
)
(p-1)! \equiv-1\pmod{p}
(p−1)!≡−1(modp)
值得注意的是,威尔逊逆定理依旧成立。
证明很简单,这里不给出详细证明。
0x82 费马小定理
若 p 为素数,a为正整数,且 a ,p 互质,则:
a
p
−
1
≡
1
(
m
o
d
p
)
a^{p-1}\equiv1\pmod{p}
ap−1≡1(modp)
将化简,可得:
a
p
≡
a
(
m
o
d
p
)
a^{p}\equiv a\pmod{p}
ap≡a(modp)
这就是 费马小定理。
0x83 埃式筛法&线性筛法
埃式筛法 是一种近似于暴力的枚举方法,将比他大的质数倍的数累加起来判断最大值。
线性筛法 利用最大的因子筛掉了大部分的重复计算,去掉了多余的操作。
#include<iostream>
#include<cstdio>
using namespace std ;
const int MAXN = 1e6 ;
long long n ;
inline long long read(){
long long x = 0 ;
long long y = 1 ;
char c = getchar() ;
while(c < '0' || c > '9'){
if(c == '-')
y = -1 ;
c = getchar() ;
}
while(c <= '9' && c >= '0'){
x = x * 10 + c - '0' ;
c = getchar() ;
}
return x * y ;
}
//埃式筛法——特点:空间小、码量少 [O(n * log log n)]
bool vis1[MAXN] ;
inline void Eratosthenes_primes(long long num){
memset(vis1 , 0 , sizeof(vis1)) ;
for(long long i = 2;i <= num;++i){
if(vis1[i] == true)
continue ;
printf("%lld " , i) ;
for(long long j = i;j <= num / i;++j)
vis1[i * j] = true ;
}
}
//线性筛法——特点:时间快[O(n)]
long long prime[MAXN] ;
long long vis2[MAXN] ;
long long cnt = 0 ;
inline void primes(long long num){
memset(vis2 , 0 , sizeof(vis2)) ;//最小质因子
cnt = 0 ;//质数数量
for(long long i = 2;i <= num;++i){
if(vis2[i] == 0){
vis2[i] = i ;
prime[++cnt] = i ;
}
//给当前质数加上质因子
for(long long j = 1;j <= cnt;++j){
//i有比prime[j]更小的质因子,或者超出范围,停止循环
if(prime[j] > vis2[i] || prime[j] > num / i)
break ;
//prime[j]是合数vis2[i * prime[j]]的最小质因子
vis2[i * prime[j]] = prime[j] ;
}
}
for(int i = 1;i <= cnt;++i)
printf("%lld " , prime[i]) ;
}
int main(){
n = read() ;
Eratosthenes_primes(n) ;
printf("\n") ;
primes(n) ;
return 0 ;
}
0x84 Millar-Rabin 素数测试
利用 费马小定理 反证素数
//时间复杂度:O(log n)
#include<iostream>
#include<cstdlib>
using namespace std ;
long long n ;
inline long long read(){
long long x = 0 ,y = 1 ;
char c = getchar() ;
while(c < '0' || c > '9'){
if(c == '-')
y = -1 ;
c = getchar() ;
}
while(c >= '0' && c <= '9'){
x = x * 10 + c - '0' ;
c = getchar() ;
}
return x * y ;
}
//快速幂
inline long long quick_pow(long long base , long long up , long long mod){
long long res = 1 ;
while(up){
if(up & 1)
res = (res % mod * base) % mod ;
base = (base % mod) * base % mod ;
up >>= 1 ;
}
return res ;
}
inline bool Miller_Robbin(long long num){
if(num == 0 || num == 1)
return false ;
if(num == 2)
return true ;
for(int i = 1;i <= 30;++i){//枚举30次,保证正确率
int base = rand() % (num - 1) + 1 ;//生成随机枚举底数
if(quick_pow(base , num-1 , num) != 1)//费马小定理
return false ;
}
return true ;
}
int main(){
n = read() ;
if(Miller_Robbin(n) == true)
printf("YES") ;
else
printf("NO") ;
return 0 ;
}
0x85 欧拉定理
内容:
若 a 、m 互质,则
a
φ
(
m
)
≡
1
(
m
o
d
m
)
a^{φ (m)}\equiv1\pmod{m}
aφ(m)≡1(modm)
用途:
指数取模。【前提:p为质数!】
x
y
m
o
d
p
x^y\mod{p}
xymodp可被化简为
x
y
m
o
d
φ
(
p
)
m
o
d
p
x^{y\mod{φ (p)}}\mod{p}
xymodφ(p)modp
0x86 Pollard Rho 求大数因子
//洛谷例题P4718
//没完全解决
//输出最大的质因子!!!!!
//可能需要__int128
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int t;
long long max_factor, n;
long long gcd(long long a, long long b) {
if (b == 0) return a;
return gcd(b, a % b);
}
long long quick_pow(long long x, long long p, long long mod) { // 快速幂
long long ans = 1;
while (p) {
if (p & 1) ans = (__int128)ans * x % mod;
x = (__int128)x * x % mod;
p >>= 1;
}
return ans;
}
bool Miller_Rabin(long long p) { // 判断素数
if (p < 2) return 0;
if (p == 2) return 1;
if (p == 3) return 1;
long long d = p - 1, r = 0;
while (!(d & 1)) ++r, d >>= 1; // 将d处理为奇数
for (long long k = 0; k < 10; ++k) {
long long a = rand() % (p - 2) + 2;
long long x = quick_pow(a, d, p);
if (x == 1 || x == p - 1) continue;
for (int i = 0; i < r - 1; ++i) {
x = (__int128)x * x % p;
if (x == p - 1) break;
}
if (x != p - 1) return 0;
}
return 1;
}
long long Pollard_Rho(long long x) {
long long s = 0, t = 0;
long long c = (long long)rand() % (x - 1) + 1;
int step = 0, goal = 1;
long long val = 1;
for (goal = 1;; goal *= 2, s = t, val = 1) { // 倍增优化
for (step = 1; step <= goal; ++step) {
t = ((__int128)t * t + c) % x;//生成伪随机函数
val = (__int128)val * abs(t - s) % x;
if ((step % 127) == 0) {
long long d = gcd(val, x);
if (d > 1) return d;
}
}
long long d = gcd(val, x);
if (d > 1) return d;
}
}
void fac(long long x) {
if (x <= max_factor || x < 2) return;
if (Miller_Rabin(x)) { // 如果x为质数
max_factor = max(max_factor, x); // 更新答案
return;
}
long long p = x;
while (p >= x) p = Pollard_Rho(x); // 使用该算法
while ((x % p) == 0) x /= p;
fac(x), fac(p); // 继续向下分解x和p
}
int main() {
scanf("%d", &t);
while (t--) {
srand((unsigned)time(NULL));
max_factor = 0;
scanf("%lld", &n);
fac(n);
if (max_factor == n) // 最大的质因数即自己
printf("Prime\n");
else
printf("%lld\n", max_factor);
}
return 0;
}
参考资料:博客园