Appearance
扩展欧几里得算法
用于求解
将上式展开:
因为只要求找到一组合法解,所以可以构造:
所以,在辗转相除法求解
递归边界是
解得:
感性地理解,每次
那么,
所以取
上述过程只是求出了
cpp
void exgcd(int a, int b, int &x, int &y) {
int t;
if (!b) {
x = 1, y = 0;
} else {
exgcd(b, a % b, x, y);
t = x;
x = y;
y = t - (a / b) * y;
}
}考虑通解:
因为
已知
用
所以
综上,原方程关于
求解二元一次方程整数解
裴蜀引理:
有整数解当且仅当 。
广义裴蜀引理:
有整数解当且仅当
根据「裴蜀引理」,二元一次方程
使用 exgcd 求出
代入原方程,可得:
原方程的通解为:
求解线性同余方程
对于线性同余方程
cpp
int tyfc(int a, int b, int c) {
b %= c;
int x, y;
if (b % gcd(a, c))
return -1;
exgcd(a, c, x, y);
int g = gcd(a, c);
x *= b / g;
c /= g;
x = (x % c + c) % c;
return x;
}欧拉定理
费马小定理:
。
费马小定理是欧拉定理在
扩展欧拉定理
用于大指数取模。
欧拉降幂
求
结论是一个数自取
简单证明:
是偶数
所以至少是两次缩小一半规模。
卢卡斯定理
对于质模数
Lucas 定理揭示了,组合数
即:
对于
所以时间复杂度:
cpp
int C(int x, int y, int mod) {
auto calc = [](int x, int y, int mod) -> int {
if (x < y)
return 0;
if (x == 0 || y == 0)
return 1;
return fac[x] * inv[y] % mod * inv[x - y] % mod;
};
int res = 1;
while (x && y) {
int X = x % mod, Y = y % mod;
res = res * calc(X, Y, mod) % mod;
x /= mod;
y /= mod;
}
return res;
}扩展卢卡斯定理
若模数不为质数,令
根据 crt,等价于求解
考虑求解
令
则原式可以写成
此时,问题转换成求
所以 exlucas 本质是解决了阶乘对质数幂取模的问题。
Wilson 定理:
。 推广:
。 推论:
对于
可以发现,实际上 lucas 就是模数为
对于
预处理
cpp
int phi(int x, int y) { return x * (y - 1) / y; }
int nn, a[N], b[N];
int mod[N], mem[N];
int intchina() {
int i, prod = 1, s = 0;
for (i = 1; i <= nn; i++)
prod *= a[i];
for (i = 1; i <= nn; i++) {
int t = qz(prod / a[i], phi(a[i], mod[i]) - 1, a[i]);
s = (s + b[i] * (prod / a[i]) * t) % prod;
}
return s;
}
int n, m, p;
void init(int p, int mod) {
int i;
mem[0] = 1;
for (i = 1; i <= mod; i++) {
mem[i] = mem[i - 1];
if (i % p)
mem[i] = mem[i] * i % mod;
}
}
int l(int x, int p) {
int res = p, ans = 0;
while (res <= x) {
ans += x / res;
res *= p;
}
return ans;
}
int g(int x, int mod) {
if (x <= mod)
return mem[x];
if ((x / mod) % 2 == 0)
return g(x % mod, mod) % mod;
return (-g(x % mod, mod) % mod + mod) % mod;
}
int f(int x, int p, int mod) {
if (x <= 1)
return 1;
return f(x / p, p, mod) * g(x, mod) % mod;
}
int C(int n, int m, int p) {
for (int i = 2; i <= p / i; i++) {
int res = 1;
while (p % i == 0) {
res *= i;
p /= i;
}
if (res > 1) {
a[++nn] = res;
mod[nn] = i;
}
}
if (p > 1) {
a[++nn] = p;
mod[nn] = p;
}
for (int i = 1; i <= nn; i++) {
int base = l(n, mod[i]) - l(m, mod[i]) - l(n - m, mod[i]);
int aa = qz(mod[i], base, a[i]);
int res1, res2;
init(mod[i], a[i]);
res1 = f(n, mod[i], a[i]),
res2 = f(m, mod[i], a[i]) * f(n - m, mod[i], a[i]) % a[i];
aa = aa * res1 % a[i];
aa = aa * qz(res2, (a[i] - a[i] / mod[i]) - 1, a[i]);
b[i] = aa;
}
return intchina();
}BSGS
bsgs 用于求解指数同余方程,即:
其基本原理是任意一个
枚举
具体到原问题中,首先确定
当
预处理
所以可以发现,bsgs 建立在
有求逆元和 map 询问的存在,时间复杂度:
小优化:
考虑将
预处理 hashmap 的时间复杂度视作
省去了求逆元的时间复杂度。(但是上式仍建立在逆元存在的基础上)
或者实际上本质上是求
cpp
struct BSGS {
unordered_map<int, int> mp;
int bsgs(int a, int b, int p) {
if (b == 1)
return 0;
b %= p;
int sqt = ceil(sqrt(p));
int A = b;
mp.clear();
for (int i = 0; i < sqt; i++) {
mp[A] = i;
A = A * a % p;
}
int base = qz(a, sqt, p);
A = 1;
for (int i = 1; i <= sqt; i++) {
A = A * base % p;
if (mp.count(A))
return i * sqt - mp[A];
}
return -1;
}
};扩展 BSGS
用于求解:
因为
若
此时,原式为:
采用 bsgs 求解后,
时间复杂度:
cpp
struct ExBSGS {
int exbsgs(int a, int b, int c) {
int sqt, base, i, A = 1;
hashmap.clear();
for (int i = 0; i < 32; i++) {
if (A % c == b)
return i;
A = (A * a) % c;
}
int d = 1, cnt = 0;
while (gcd(a, c) != 1) {
int g = gcd(a, c);
if (b % g != 0)
return -1;
b /= g;
c /= g;
d = (d * a / g) % c;
cnt++;
}
sqt = ceil(sqrt(c));
A = b;
for (int i = 0; i < sqt; i++) {
hashmap[A] = i;
A = A * a % c;
}
base = qz(a, sqt, c);
A = d * base % c;
for (int i = 1; i <= sqt; i++) {
if (hashmap.find(A))
return i * sqt - hashmap[A] + cnt;
A = A * base % c;
}
return -1;
}
int calc(int b, int n, int p) {
b %= p, n %= p;
if (n == 1 || p == 1)
return 0;
if (b == 0 && n != 0)
return -1;
return exbsgs(b, n, p);
}
};离散对数
对于模数
记作
所以求解底数是原根时的特殊指数同余方程,可以使用 bsgs 解决。
模数为质数的 N 次剩余
对于
一般意义下的 N 次剩余
对于模数非质数的 N 次剩余,利用中国剩余定理,将模数写成
特别地,
中国剩余定理
用于求解线性同余方程组
令
则原同余方程组的解为
可以证明,线性同余方程组在模意义下解唯一,所以上述解即为原方程组的唯一解。
时间复杂度:
注:若不保证模数为质数,仅保证两两互质,那么可以使用欧拉定理预处理欧拉函数求逆元。或直接使用 exgcd 求解逆元。
cpp
struct CRT {
int intchina(vector<int> &r, vector<int> &mod, int n) {
int M = 1;
for (int i = 1; i <= n; i++)
M *= mod[i];
int ans = 0;
for (int i = 1; i <= n; i++) {
if (mod[i] == 1)
continue;
int now = M / mod[i];
int inv = qz(now % mod[i], phi[mod[i]] - 1, mod[i]);
ans = (ans + r[i] * now * inv) % M;
}
return ans;
}
};garner 算法
若
我们可以用以下形式的式子(称作
garner 算法用来计算系数
令
把
代入第二个方程得出:
方程两边减
类似地,我们可以得到:
时间复杂度:
三模数 NTT 实现的任意模数 NTT 就是这个原理。
cpp
int Garner(vector<int> &r, vector<int> &mod, int n) {
vector<int> x(n + 1);
vector<vector<int>> iv(n + 1, vector<int>(n + 1));
for (int i = 1; i <= n; i++) {
for (int j = i + 1; j <= n; j++) {
iv[i][j] = qz(mod[i], mod[j] - 2, mod[j]);
}
}
for (int i = 1; i <= n; i++) {
x[i] = r[i];
for (int j = 1; j < i; j++) {
x[i] = iv[j][i] * (x[i] - x[j]) % mod[i];
if (x[i] < 0)
x[i] += mod[i];
}
}
int res = x[1];
int now = 1;
for (int i = 2; i <= n; i++) {
now = now * mod[i - 1];
res += now * x[i];
}
return res;
}扩展中国剩余定理
用于求解线性同余方程组
对于两个同余方程
写成二元一次方程的形式:
使用 exgcd 求解即可。
注意会出现
将解出的
至此,成功将两个同余方程合并成了一个同余方程。
依次类推,合并
实际上此处 crt 和 excrt 讨论的都是
对于一般的同余方程
时间复杂度:
cpp
int eyyc(int a, int b, int c) {
int g = gcd(a, b);
if (c % g)
return -1;
int x, y;
exgcd(a, b, x, y);
x *= c / g;
b /= g;
x = (x % b + b) % b;
return x;
}
struct ExCRT {
int intchina(vector<int> &r, vector<int> &mod, int n) {
int R = r[1], M = mod[1];
for (int i = 2; i <= n; i++) {
int x = eyyc(M, mod[i], R - r[i]); // b 取正值,保证求解得到的一定是非负值
if (x == -1)
return -1;
R -= x * M;
M = lcm(M, mod[i]);
R %= M;
}
if (R < 0)
R += M;
return R;
}
};同余最短路:
同余最短路用于求解模意义下从
例如给定
若选中任一
即可以关于
在每个同余类中求出最小的可以表示的数,即可求出最小的可以表示的数。
在模意义下,多一个
转圈法求解同余最短路问题:
本质是:
所以对于不同的
转移
时间复杂度:
