数学
最大公约数 最小公倍数 唯一分解定理
\(\gcd(a, b) = \gcd(b, a\%b)\)
// 1. 最大公约数(GCD)- 欧几里得算法(迭代版)
int gcd(int a,int b)
{
if(b==0) return a;
else return gcd(b,a%b);
}
// 2. 最小公倍数(LCM)- 基于GCD计算
long long lcm(long long a, long long b) {
if (a == 0 || b == 0) return 0; // 0和任何数的最小公倍数是0
return (a / gcd(a, b)) * b; // 先除后乘避免溢出
}
分解质因数 判定素数 素数筛
分解质因数
vector<int> primeFactorization(int n) {
vector<int> factors;
for (int i = 2; i * i <= n; i++) {
while (n % i == 0) {
factors.push_back(i);
n /= i;
}
}
if (n > 1) factors.push_back(n);
return factors;
}
素数筛:使用素数的倍数去筛掉合数
但是一个合数会被多次素数筛除影响效率
bool isnp[MAXN]; // is not prime: 不是素数
void init(int n)
{
for (int i = 2; i * i <= n; i++)
if (!isnp[i])
for (int j = i * i; j <= n; j += i)
isnp[j] = 1;
}
引入欧拉筛:不会重复筛除
合数只筛一次 保证是被最小质因数筛除
质数就需要遍历全部筛除
bool isnp[MAXN]; // 标记是否为合数
vector<int> primes; // 存储素数表
void euler_sieve(int n) {
for (int i = 2; i <= n; ++i) {
if (!isnp[i]) primes.push_back(i); // i是素数,加入表
for (int p : primes) {
if (p * i > n) break; // 超出范围,停止
isnp[p * i] = true; // 标记p*i为合数
if (i % p == 0) break; // 保证p是i的最小质因子,避免重复筛
}
}
}
快速幂
非递归快速幂
private static long powMod(long a, long b, long mod) {
long result = 1;
a %= mod; // 防止a过大
while (b > 0) {
if ((b & 1) != 0) result = (result * a) % mod;
a = (a * a) % mod;
b >>= 1;
}
return result;
}
矩阵快速幂
应用:①先根据题意求出递推公式 ②根据递推公式写出它的转移矩阵T,即TX^ (n-1)=X^ n。注意此转移矩阵的系数不包含未知数,这样才可以进行第③步 ③求出转移矩阵T后,TX^ (n-1)=X^ n,利用矩阵的快速幂求出X^(n-1)的值,最后乘以系数矩阵T(也称为转移矩阵)得出矩阵X ^ n的值
private static long[][] multiply(long[][] a, long[][] b,int n,int mod){
long[][] result = new long[80][80];
for(int i=1;i<=2*n;i++){
for(int j=1;j<=2*n;j++){
for(int k=1;k<=2*n;k++){
result[i][j] += ((a[i][k]*b[k][j])%mod);
result[i][j] %=mod;
}
}
}
return result;
}
private static long[][] quick(int k,int n,int mod){
long[][] cur = new long[80][80];
long[][] result = new long[80][80];
for(int i=1;i<=2*n;i++){
result[i][i]=1;
}
cur = matrix;
while(k!=0){
if(k%2 ==1) result = multiply(result, cur, n, mod);
cur = multiply(cur, cur, n, mod);
k/=2;
}
return result;
}
exgcd
\(\gcd(a, 0) = a\)
\(ax_1 + by_1 = \gcd(a, b) =bx_2 + (a \bmod b)y_2 = \gcd(b, a \bmod b)\)
static long x,y;
public static long exgcd(long a,long b){
if (b==0){
x=1;
y=0;
return a;
}
long res=exgcd(b,a%b);
long k=x;
x=y;
y=k-a/b*y;
return res;
}
逆元
费马小定理
对于整数 \(a\) 与正整数模 \(m\),若 \(\gcd(a,m)=1\),则在同余类环 \(\mathbb{Z}/m\mathbb{Z}\) 中存在唯一的 \(x\) 使得 \(a\,x \equiv 1 \pmod m.\) 这时称 \(x\) 是 \(a\) 关于模 \(m\) 的逆元,记为 \(x \equiv a^{-1}\pmod m.\)
若 a 与质数 p 互质\(a^{p-1} \equiv 1 \pmod{p}\)
也就是说,当模数为质数,且需要求逆元的数与模数互质时,可通过快速幂直接求取其逆元。
long long quickpow(long long a,long long n)
{
a%=mod;
long long res=1;
while(n>0)
{
if(n&1) res=(res*a)%mod;
a=(a*a)%mod;
n>>=1;
}
return res;
}
long long inv(long long a)
{
return quickpow(a,mod-2);
}
exgcd求逆元
public static long exgcd(long a,long b){
if (b==0){
x=1;
y=0;
return a;
}
long res=exgcd(b,a%b);
long k=x;
x=y;
y=k-a/b*y;
return res;
}
public static long invExgcd(long a,long p){
long g=exgcd(a,p);
if (g!=1) return -1;
else return (x%p+p)%p;
}
快速求多个逆元
int inv[1000000];
void find_inv(int last,int p)//求1~last所有数模p意义下的逆元
{
inv[1]=1;//1的逆元就是1本身
for(int i=2;i<=last;i++)
inv[i]=(long long)(p-p/i)*(inv[p%i])%p;
//注意longlong,否则有可能导致溢出
}
欧拉函数 欧拉降幂
数论中的欧拉函数 \(\varphi(x)\) 是一个非常重要的函数,它定义为小于(或不大于,这里是一样的)\(x\) 但与 \(x\) 互质的正整数的数量。例如\(\varphi(12)=4,\) 因为 \(1,5,7,11\) 与 \(12\) 互质。特别地,规定\(\varphi(1)=1.\) 对于欧拉函数,主要有如下性质:
- 若 \(p\) 是质数,则 \(\varphi\bigl(p^n\bigr)=p^{n-1}(p-1).\)
- 若 \(a\mid x\),则 \(\varphi(a x)=a\,\varphi(x).\)
- 若 \(\gcd(a,b)=1\),则 \(\varphi(a)\,\varphi(b)=\varphi(a b).\)
我们把正整数质因数分解: \(x = p_1^{k_1} p_2^{k_2} \cdots p_n^{k_n}\) 所有 \(p_i^{k_i}\) 两两互质,由欧拉函数的性质有: \(\varphi(x) = p_1^{k_1 - 1}(p_1 - 1) \cdot p_2^{k_2 - 1}(p_2 - 1) \cdots p_n^{k_n - 1}(p_n - 1)\) 即: \(\varphi(x) = x \cdot \frac{p_1 - 1}{p_1} \cdot \frac{p_2 - 1}{p_2} \cdots \frac{p_n - 1}{p_n}\)
//理解模拟 慢
int phi(int n)
{
int res = n;
for (int i = 2; i * i <= n; i++)
{
if (n % i == 0)
res = res / i * (i - 1); // 先除再乘防止溢出
while (n % i == 0) // 每个质因数只处理一次,可以把已经找到的质因数除干净
n /= i;
}
if (n > 1)
res = res / n * (n - 1); // 最后剩下的部分也是原来的n的质因数
return res;
}
int phi[MAXN];
void init(int n)
{
for (int i = 1; i <= n; i++)
phi[i] = i; // 除1外没有数的欧拉函数是本身,所以如果phi[i] = i则说明未被筛到
for (int i = 2; i <= n; i++)
if (phi[i] == i) // 未被筛到
for (int j = i; j <= n; j += i) // 所有含有该因子的数都进行一次操作
phi[j] = phi[j] / i * (i - 1);
}
void init(int n)
{
phi[1] = 1;
for (int i = 2; i <= n; i++)
{
if (!isnp[i])
primes.push_back(i), phi[i] = i - 1; // 性质一,指数为1的情形
for (int p : primes)
{
if (p * i > n)
break;
isnp[p * i] = 1;
if (i % p == 0)
{
phi[p * i] = phi[i] * p; // 性质二
break;
}
else
phi[p * i] = phi[p] * phi[i]; // 这时肯定互质,用性质三
}
}
}
\(a^b \mod m = \begin{cases} a^{b \mod \varphi(m)} \mod m, & \gcd(a, m) = 1, \\ a^{b \mod \varphi(m) + \varphi(m)} \mod m, & \gcd(a, m) \neq 1 \ \text{且} \ b \geq \varphi(m), \\ a^b \mod m, & \text{其他情况}. \end{cases}\)
min25筛
杜教筛
排列组合 斯特林数
卡特兰数
\(H_n = \begin{cases} \sum_{i=1}^{n} H_{i-1} H_{n-i} & n \geq 2, n \in \mathbf{N_{+}}\\ 1 & n = 0, 1 \end{cases}\)
\(H_n = \frac{H_{n-1} (4n-2)}{n+1}\)
\(H_n = \binom{2n}{n} - \binom{2n}{n-1}\)