先看个例子:112024是否为19的倍数? 112024先变为->11202和4,然后进行11202+2*4=11210 然后继续变为->1121和0,然后进行1121+2*0=1121 然后继续变为->112和1,然后进行112+2*1=114 然后继续变为->11和4,然后进行11+2*4=19,19是19的倍数,所以原数是19的倍数。 ---前置知识--- a|b,a|c => a|(b±c) A = 10m + n (m位其他位数的数值,n为个位数字) B = 10(m + 2n) = 10m + 20n (乘的10对是否是19的倍数没有影响,如果要有影响也要是后面产生的) C = B - A = 19n A = B - C (C已经是19的倍数了,如果B也是19的倍数,那么A就是19的倍数)
----------------------------------------------------------------------------------------- 一般性: 首先我们先确定好要是以谁为倍数。 确定好以谁倍数(设为x)之后,对x做乘积,使之满足各位是1或是9。 假设是17,那么合适的值为51。 因为我们要截掉尾巴,所以就是要加上几n或是减掉几n使之没有个位上的值。 A = 10m + n 进行:A - 51n = 10(m - 5n) 这样之后就按 m - 5n 来看是否为17的倍数。
voiddivide(int x) { for (int i = 2; i <= x / i; i ++ ) if (x % i == 0) { int s = 0; while (x % i == 0) x /= i, s ++ ; cout << i << ' ' << s << endl; } if (x > 1) cout << x << ' ' << 1 << endl; // n中最多只包含一个大于sqrt(n)的质因子 cout << endl; }
vector<int> get_divisors(int x) { vector<int> res; for (int i = 1; i <= x / i; i ++ ) if (x % i == 0) { res.push_back(i); if (i != x / i) res.push_back(x / i); } sort(res.begin(), res.end()); return res; }
1∼N−1 中与 N 互质的数的个数被称为欧拉函数,记为ϕ(N)。
若在算数基本定理中,N=p1a1p2a2...pmam,则:
\phi(N)=N\cdot\frac{p_1-1}{p_1}\cdot\frac{p_2-1}{p_2}\cdot...\cdot\frac{p_m-1}
1 2 3 4 5 6 7 8 9 10 11 12 13
intphi(int 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);
return res; }
欧拉定理:
定义:设 m 是正整数,(a,m)=1,则 aφ(m)≡1(modm)
** 证明:** 由定理 D,设{x1,x2,…,xφ(m)} 是模 m 的一个缩系,则 {ax1,ax2,…,axφ(m)} 也是模 m 的缩系。
** 裴蜀定理:** 设 a,b∈Z,a=0,b=0,集合 A={xa+yb∣x,y∈Z},d 为 A 中最小的正整数,则 (a,b)=d 。
证明:
①首先证明 d 的存在性,即集合 A 中一定存在最小正整数。集合 A 中的元素为整数 a 和 b 的整系数线性组合,由于 1×a+0×b 和 (−1)×a+0×b 中必有一个正整数,所以集合 A 中至少存在一个正整数。由正整数的良序性,集合 A 中的所有正整数中必然存在一个最小值 d,不妨设 d=sa+tb ,其中 s,t∈Z。
②接着证明 d 是 a 和 b 的公因数。已知对于整数 a 和 d ,存在整数 q 和 r 使得 a=qd+r,0≤r<d 进而 r=a−qd=a−q(sa+tb)=(1−qs)a−qtb。因此 $r\in A $,又因为 d 是 A 中的最小正整数,且有0≤r<d,所以只能是 r=0, 于是 a=qd,即 d∣a,同理可知 d∣b。
③最后证明 d 是 a 和 b 的最大公因数。设 c 是 a 和 b 的任何一个公因数,则 c∣a,c∣b,由于 d=sa+tb,故由定理 1 可知 c∣d,又因为 d>0,所以 d≥c。证毕。
推论:设 a,b∈Z ,当 (a,b)=1 时,一定存在整数 s 和 t , 使得 sa+tb=1。
设r0,r1∈Z,r0>r1>0,当 (r0,r1)=1 时,假设存在整系数 s 和 t 使得 sr0+tr1=1 上述等式两边对r0 取模,得到 sr_0 + tr_1 \equiv 1 \, \pmod
// 求x, y,使得ax + by = gcd(a, b) intexgcd(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; }
// a[N][N]是增广矩阵 intgauss() { int c, r; for (c = 0, r = 0; c < n; c ++ ) { int t = r; for (int i = r; 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; i ++ ) swap(a[t][i], a[r][i]); // 将绝对值最大的行换到最顶端 for (int i = n; i >= c; i -- ) a[r][i] /= a[r][c]; // 将当前行的首位变成1,一定要倒着进行。如何正着开始,第一个就会之间变为1,导致后面除的都是1。 for (int i = r + 1; i < n; i ++ ) // 用当前行将下面所有的列消成0 if (fabs(a[i][c]) > eps) // 浮点数存储0的时候会有精度问题,需要大于一个很小的数判断是否是0 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) return2; // 无解 return1; // 有无穷多组解 }
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];
// 首先预处理出所有阶乘取模的余数fact[N],以及所有阶乘取模的逆元infact[N] // 如果取模的数是质数,可以用费马小定理求逆元 intqmi(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; }
若p是质数,则对于任意整数 1 <= m <= n,有: C(n, m) = C(n % p, m % p) * C(n / p, m / p) (mod p) intqmi(int a, int k, int p) // 快速幂模板 { int res = 1 % p; while (k) { if (k & 1) res = (LL)res * a % p; a = (LL)a * a % p; k >>= 1; } return res; }
intC(int a, int b, int p)// 通过定理求组合数C(a, b) { if (a < b) return0;
LL x = 1, y = 1; // x是分子,y是分母 for (int i = a, j = 1; j <= b; i --, j ++ ) { x = (LL)x * i % p; y = (LL) y * j % p; }
return x * (LL)qmi(y, p - 2, p) % p; }
intlucas(LL a, LL b, int p) { if (a < p && b < p) returnC(a, b, p); return (LL)C(a % p, b % p, p) * lucas(a / p, b / p, p) % p; }
当我们需要求出组合数的真实值,而非对某个数的余数时,分解质因数的方式比较好用: 1. 筛法求出范围内的所有质数 2. 通过 C(a, b) = a! / b! / (a - b)! 这个公式求出每个质因子的次数。 n! 中p的次数是 n / p + n / p^2 + n / p^3 + ... 3. 用高精度乘法将所有质因子相乘
int primes[N], cnt; // 存储所有质数 int sum[N]; // 存储每个质数的次数 bool st[N]; // 存储每个数是否已被筛掉
voidget_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; } } }
intget(int n, int p)// 求n!中的次数 { int res = 0; while (n) { res += n / p; n /= p; } return res; }
vector<int> mul(vector<int> a, int b)// 高精度乘低精度模板 { vector<int> c; int t = 0; for (int i = 0; i < a.size(); i ++ ) { t += a[i] * b; c.push_back(t % 10); t /= 10; }
while (t) { c.push_back(t % 10); t /= 10; }
return c; }
get_primes(a); // 预处理范围内的所有质数
for (int i = 0; i < cnt; i ++ ) // 求每个质因数的次数 { int p = primes[i]; sum[i] = get(a, p) - get(b, p) - get(a - b, p); }
vector<int> res; res.push_back(1);
for (int i = 0; i < cnt; i ++ ) // 用高精度乘法将所有质因子相乘 for (int j = 0; j < sum[i]; j ++ ) res = mul(res, primes[i]);