若一个正整数 N N N为合数(除了能被1和自身以外的数整除),则存在一个能整除 N N N的数 T T T,其中 2 ≤ T ≤ N 2\leq T\leq \sqrt N 2≤T≤N。
bool is_prime(int n)
{
if (n < 2) return false;
for (int i = 2; i < sqrt(n); ++ i )
if (n % i == 0) return false;
return true;
}
基本思想:任意整数 x x x的倍数 2 x , 3 x , . . . [ N / x ] ∗ x 2x, 3x, ...[N/x]*x 2x,3x,...[N/x]∗x都不是质数。
void primes(int n)
{
memset(v, 0, sizeof(v)); //合数标记
for (int i = 2; i <= n; ++ i )
{
if (v[i]) continue;
cout << i << endl; // i是质数
for (int j = i; j <= n/i; ++ j ) v[i*j] = 1;
}
}
Eratosthenes筛法的时间复杂度为 O ( N l o g l o g N ) O(NloglogN) O(NloglogN)。该算法效率非常接近线性。
线性筛法通过“从大到小累计质因子”的方式标记合数,即让12只有 3 ∗ 2 ∗ 2 3*2*2 3∗2∗2一种产生方式。
int primes[N], cnt = 0; // primes[]存储所有素数
bool st[N]; // st[x]存储x是否被筛掉
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;
}
}
}
定理:任何一个大于1的正整数都能唯一分解为有限个质数的乘积,可写作:
N = p 1 c 1 p 2 c 2 . . . p m c m N = p^{c_1}_1p^{c_2}_2...p^{c_m}_m N=p1c1p2c2...pmcm
其中 c i c_i ci都是正整数, p i p_i pi都是质数,且满足 p 1 < p 2 < . . . < p m p_1
void divide(int n)
{
m = 0;
for (int i = 2; i <= sqrt(n); ++ i )
{
if (n % i == 0)
{
p[++m] = i, c[m] = 0;
while (n % i == 0) n /= i, c[m]++;
}
}
if (n > 1) // n是质数
p[++m] = n, c[m] = 1;
for (int i = 1; i <= m; ++ i )
cout << p[i] << " " << c[i] << endl;
}
定义:若整数 n n n除以整数 d d d的余数为0,即 d d d能整出 n n n,则称 d d d是 n n n的约数,记为 d ∣ n d|n d∣n。
关于约数的一个定理:一个数有奇数个约数,那么这个数是平方数。
在算术基本定理中,若正整数 N N N被唯一分解为 N = p 1 c 1 p 2 c 2 . . . p m c m N = p^{c_1}_1p^{c_2}_2...p^{c_m}_m N=p1c1p2c2...pmcm,其中 c i c_i ci都是正整数, p i p_i pi都是质数,且满足 p 1 < p 2 < . . . < p m p_1
{ p 1 c 1 p 2 c 2 . . . p m c m } , 其 中 0 ≤ b i ≤ c i \{p^{c_1}_1p^{c_2}_2...p^{c_m}_m\},其中0\leq b_i\leq c_i {p1c1p2c2...pmcm},其中0≤bi≤ci
N N N的正约数个数为:
( c 1 + 1 ) ∗ ( c 2 + 1 ) ∗ . . . ( c m + 1 ) = ∏ i = 1 m ( c i + 1 ) (c_1+1)*(c_2+1)*...(c_m+1)=\prod^{m}_{i=1}(c_i+1) (c1+1)∗(c2+1)∗...(cm+1)=i=1∏m(ci+1)
N N N的所有正约数的和为:
( 1 + p 1 + p 1 2 + . . . + p 1 c 1 ) ∗ . . . ∗ ( 1 + p m + p m 2 + . . . + p m c m ) = ∏ i = 1 m ( ∑ j = 0 c i ( p i ) j ) (1+p_1+p_1^2+...+p_1^{c_1})*...*(1+p_m+p_m^2+...+p_m^{c_m}) = \prod^m_{i=1}(\sum^{c_i}_{j=0}(p_i)^j) (1+p1+p12+...+p1c1)∗...∗(1+pm+pm2+...+pmcm)=i=1∏m(j=0∑ci(pi)j)
若 d ≥ N d\geq\sqrt N d≥N是 N N N的约数,则 N / d ≤ N N/d\leq \sqrt N N/d≤N也是 N N N的约数。换言之,约数是成对出现的(除了完全平方数)。
int factor[1600], m = 0;
for (int i = 2; i*i <= n; ++ i )
{
if (n % i == 0)
{
factor[++m] = i;
if (n/i != i) factor[++m] = n/i;
}
}
for (int i = 1; i <= m; ++ i )
cout << factor[i] << endl;
一个整数 N N N的约数个数上界为 2 N 2\sqrt N 2N。
对于每个数 d d d, 1 ∼ N 1\sim N 1∼N中以 d d d为约数的数就是 d d d的倍数 d , 2 d , . . . [ n / d ] ∗ d d,2d,...[n/d]*d d,2d,...[n/d]∗d。
vector<int> factor[500010];
for (int i = 1; i <= n; ++ i )
{
for (int j = 1; j <= n/i; ++ j )
factor[i*j].push_back(i);
}
for (int i = 1; i <= n; ++ i )
{
for (auto &x : factor[i])
{
cout << x << " ";
}
puts("");
}
上述算法的时间复杂度为 O ( N l o g N ) O(NlogN) O(NlogN)。
1 ∼ N 1\sim N 1∼N每个数的约数个数的总和大约为 N l o g N NlogN NlogN。
定理: ∀ a , b ⊂ N , g c d ( a , b ) ∗ l c m ( a , b ) = a ∗ b \forall a,b\subset \N, gcd(a,b) *lcm(a,b)=a*b ∀a,b⊂N,gcd(a,b)∗lcm(a,b)=a∗b。
int gcd(int a, int b)
{
return b ? gcd(b, a%b) : a;
}
定义: ∀ a , b ⊂ N , g c d ( a , b ) = 1 \forall a,b\subset \N, gcd(a,b) =1 ∀a,b⊂N,gcd(a,b)=1,则称a,b互质。
1 ∼ N 1\sim N 1∼N中与 N N N互质的数的个数被称为欧拉函数,记为 φ ( N ) \varphi (N) φ(N)。
若在算术基本定理中, N = p 1 c 1 p 2 c 2 . . . p m c m N = p^{c_1}_1p^{c_2}_2...p^{c_m}_m N=p1c1p2c2...pmcm,则
φ ( N ) = N ∗ p 1 − 1 p 1 ∗ p 2 − 1 p 2 ∗ . . . ∗ p m − 1 p m = N ∗ ∏ 质 数 p ∣ N ( 1 − 1 p ) \varphi (N) = N*\frac{p_1-1}{p_1}*\frac{p_2-1}{p_2}*...*\frac{p_m-1}{p_m}=N*\prod_{质数p|N}(1-\frac{1}{p}) φ(N)=N∗p1p1−1∗p2p2−1∗...∗pmpm−1=N∗质数p∣N∏(1−p1)
int phi(int n)
{
int ans = n;
for (int i = 2; i < sqrt(n); ++ i )
{
if (n % i == 0)
{
ans = ans*(i-1)/i;
while (n % i == 0) n /= i;
}
}
if (n > 1) ans = ans*(n-1)/n;
return ans;
}
欧拉函数的常用性质:
int primes[N], euler[N], cnt;
bool st[N];
// 质数存在primes[]中,euler[i] 表示
// i的欧拉函数
void get_eulers(int n)
{
euler[1] = 1;
for (int i = 2; i <= n; i ++ )
{
if (!st[i])
{
primes[cnt ++ ] = i;
euler[i] = i - 1;
}
for (int j = 0; primes[j] <= n/i; j ++ )
{
st[primes[j] * i] = true;
if (i % primes[j] == 0)
{
euler[i * primes[j]] = euler[i] * primes[j];
break;
}
euler[i * primes[j]] = euler[i] * (primes[j] - 1);
}
}
}
定义:若整数 a a a和 b b b除以正整数 m m m的余数相等,则称 a , b a,b a,b模m同余,记为 a ≡ b ( m o d m ) a\equiv b(mod\ m) a≡b(mod m)。
费马小定理:
若 p p p是质数,而整数 a a a不是 p p p的倍数,有 a p ≡ a ( m o d p ) a^p\equiv a(mod\ p) ap≡a(mod p)。
欧拉定理:
若正整数 a , n a,n a,n互质,则 a φ ( n ) ≡ 1 ( m o d n ) a^{\varphi(n)} \equiv 1(mod \ n) aφ(n)≡1(mod n),其中 φ ( n ) \varphi(n) φ(n)为欧拉函数。
欧拉定理的推论:
若正整数 a , b a,b a,b互质,则对于任意正整数 b b b, a b ≡ a b m o d φ ( n ) ( m o d n ) a^b \equiv a^{b\ mod\ \varphi(n)}(mod \ n) ab≡ab mod φ(n)(mod n)。
裴蜀定理:
对于任意整数 a , b a,b a,b,存在一对整数 x , y x,y x,y,满足 a x + b y = g c d ( a , b ) ax+by=gcd(a,b) ax+by=gcd(a,b)。
int exgcd(int a, int b, int& x, int& y)
{
if (b == 0) { x = 1, y = 0; return a; }
int d = exgcd(b, a%b, x, y);
int z = x; x = y; y = z-y*(a/b);
return d;
}
乘法逆元:
若整数 b , m b,m b,m互质,并且 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(mod m)。称 x x x为 b b b的模 m m m乘法逆元,记为 b − 1 ( m o d m ) b^{-1}(mod\ m) b−1(mod m)。
因为 a / b ≡ a ∗ b − 1 ≡ a / b ∗ b ∗ b − 1 ( m o d m ) a/b\equiv a*b^{-1}\equiv a/b*b*b^{-1}(mod\ m) a/b≡a∗b−1≡a/b∗b∗b−1(mod m),所以 b ∗ b − 1 ≡ 1 ( m o d m ) b*b^{-1}\equiv 1(mod\ m) b∗b−1≡1(mod m)。
当模数m为质数时, b m − 2 b^{m-2} bm−2即为 b b b的乘法逆元。如果只是保证 b , m b,m b,m互质,那么乘法逆元可通过解同余方程 b ∗ x ≡ 1 ( m o d m ) b*x\equiv 1(mod\ m) b∗x≡1(mod m)得到。
给定整数 a , b , m a,b,m a,b,m,求一个整数 x x x满足 a ∗ x ≡ b ( m o d m ) a*x\equiv b(mod\ m) a∗x≡b(mod m),或者给出无解。因为未知数的指数为1,所以我们称之为一次同余方程,也称为线性同余方程。
a ∗ x ≡ b ( m o d m ) a*x\equiv b(mod\ m) a∗x≡b(mod m)等价于 a ∗ x − b a*x-b a∗x−b是 m m m的倍数,不妨设为 − y -y −y倍。于是,该方程可以改写为 a ∗ x + m ∗ y = b a*x+m*y=b a∗x+m∗y=b。
根据前面裴蜀定理的证明,线性同余方程有解当且仅当 g c d ( a , m ) ∣ b gcd(a,m)|b gcd(a,m)∣b。
中国剩余定理:
设 m 1 , m 2 , . . . , m n m_1,m_2,...,m_n m1,m2,...,mn是两两互质的整数, m = ∏ i = 1 n m i , M i = m / m i , t i m=\prod^n_{i=1}m_i,M_i=m/m_i,t_i m=∏i=1nmi,Mi=m/mi,ti是线性同余方程 M i t i ≡ 1 ( m o d m i ) M_it_i\equiv 1(mod\ m_i) Miti≡1(mod mi)的一个解。对于任意的 n n n个整数 a 1 , a 2 , . . . , a n a_1,a_2,...,a_n a1,a2,...,an,方程组
x ≡ a 1 ( m o d m 1 ) , x ≡ a 2 ( m o d m 2 ) , . . . , x ≡ a n ( m o d m n ) x\equiv a_1(mod\ m_1),x\equiv a_2(mod\ m_2),...,x\equiv a_n(mod\ m_n) x≡a1(mod m1),x≡a2(mod m2),...,x≡an(mod mn)
有整数解,解为 x = ∑ i = 1 n a i M i t i x=\sum^n_{i=1}a_iM_it_i x=∑i=1naiMiti。
排列数:
从 n n n个不同元素中依次取出 m m m个元素排成一列,产生的不同排列的数量为:
A n m = n ! ( n − m ) ! A^m_n=\frac{n!}{(n-m)!} Anm=(n−m)!n!
组合数:
从n个不同元素中取出m个组成一个集合(不考虑顺序),产生的不同集合数量为:
C n m = n ! m ! ( n − m ) ! C^m_n=\frac{n!}{m!(n-m)!} Cnm=m!(n−m)!n!
性质:
C n m = c n n − m C_n^m=c_n^{n-m} Cnm=cnn−m
C n m = c n − 1 m + c n − 1 m − 1 C_n^m=c_{n-1}^m+c_{n-1}^{m-1} Cnm=cn−1m+cn−1m−1
C n 0 + C n 1 + . . . + C n n = 2 n C_n^0+C_n^1+...+C_n^n=2^n Cn0+Cn1+...+Cnn=2n
递归法求组合数
// c[a][b] 表示从a个中选b个的方案数
for (int i = 0; i < N; i ++ )
{
for (int j = 0; j <= i; j ++ )
{
if (!j) c[i][j] = 1;
else c[i][j] = (c[i - 1][j] + c[i - 1][j - 1]) % mod;
}
}
通过预处理逆元的方式求组合数
// 首先预处理出所有阶乘取模的余数fact[N],以及所有阶乘取模的逆元infact[N]
// 如果取模的数是质数,可以用费马小定理求逆元
int qmi(int a, int k, int p) // 快速幂模板
{
int res = 1;
while (k)
{
if (k & 1) res = (LL)res * a % p;
a = (LL)a * a % p;
k >>= 1;
}
return res;
}
// 预处理阶乘的余数和阶乘逆元的余数
fact[0] = infact[0] = 1;
for (int i = 1; i < N; i ++ )
{
fact[i] = (LL)fact[i - 1] * i % mod;
infact[i] = (LL)infact[i - 1] * qmi(i, mod - 2, mod) % mod;
}
多重集的排列数:
多重集是指包含重复元素的广义集合。设 S = { n 1 ∗ a 1 , n 2 ∗ a 2 , . . . , a k ∗ a k } S=\{n_1*a_1,n_2*a_2,...,a_k*a_k \} S={n1∗a1,n2∗a2,...,ak∗ak}是由 n 1 n_1 n1个 a 1 a_1 a1, n 2 n_2 n2个 a 2 . . . n k a_2...n_k a2...nk个 a k a_k ak组成的多重集。 S S S的全排列个数为 n ! n 1 ! n 2 ! . . . n k ! \frac{n!}{n_1!n_2!...n_k!} n1!n2!...nk!n!。
多重集的组合数:
从 S S S中取出 r ( r ≤ n i ) r(r\leq n_i) r(r≤ni)个元素组成一个多重集(不考虑元素的顺序),产生的不同多重集的数量为 C k + r − 1 k − 1 C_{k+r-1}^{k-1} Ck+r−1k−1。
Lucas定理: