同余代数
同余代数
定理相关
-
费马小定理:若 \(p\) 为质数且 \(a \bot p\) ,则 \(a^{p - 1} \equiv 1 \pmod{p}\) 。
-
欧拉定理:若 \(a \bot m\) ,则 \(a^{\varphi(m)} \equiv 1 \pmod{m}\) 。
扩展形式:
\[\]\begin{cases}
a^{b \bmod \varphi(m)}, &\gcd(a, m) = 1 \
a^b, &\gcd(a, m) \ne 1, b < \varphi(m) \
a^{(b \bmod \varphi(m)) + \varphi(m)}, &\gcd(a, m) \ne 1, b \ge \varphi(m)
\end{cases}
\pmod{m}\[\] -
Wilson 定理:对于素数 \(p\) ,有 \((p - 1)! \equiv -1 \pmod{p}\) 。
-
Kummer 定理:
- \(p\) 在 \(n!\) 的幂次等于 \(n\) 减去 \(n\) 在 \(p\) 进制下的各位数字和再除以 \(p - 1\) 。
- 当 \(p = 2\) 时幂次为 \(n - \mathrm{popcount}(n)\) 。
- \(p\) 在 \(\binom{n}{m}\) 的幂次等于 \(p\) 进制下 \(n - m\) 需要借位的次数。
- \(p\) 在 \(\binom{n + m}{n}\) 的幂次等于 \(p\) 进制下 \(n + m\) 的进位次数。
- \(\binom{n + m}{m} \bmod 2 = [n \operatorname{and} m = 0]\) 。
- \(p\) 在 \(n!\) 的幂次等于 \(n\) 减去 \(n\) 在 \(p\) 进制下的各位数字和再除以 \(p - 1\) 。
欧几里得相关
exgcd
先引入裴蜀定理:不定方程 \(ax + by = c\) 有整数解当且仅当 \(\gcd(a, b) \mid c\) 。
exgcd 通常用于求解不定方程 \(ax + by = \gcd(a, b)\) 的一组解。由裴蜀定理,该方程一定有解。
记 \(d = \gcd(a, b) = \gcd(b, a \bmod b)\) ,因此方程 \(bx + (a \bmod b) y = d\) 一定有解,设其一组解为 \(x = x', y = y'\) ,则:
因此可以不断递归,当 \(b = 0\) 时 \(x = 1, y = 0\) 即为一组解。
时间复杂度 \(O(\log V)\) 。
int exgcd(int a, int b, int &x, int &y) {
if (b) {
int g = exgcd(b, a % b, y, x);
return y -= a / b * x, g;
} else
return x = 1, y = 0, a;
}
P5656 【模板】二元一次不定方程 (exgcd)
解不定方程 \(ax + by = c\) 。
- 若无整数解则输出 \(-1\) 。
- 否则若存在正整数解,则输出:正整数解的数量、所有正整数解中 \(x\) 的最小值、所有正整数解中 \(y\) 的最小值、所有正整数解中 \(x\) 的最大值、所有正整数解中 \(y\) 的最大值。
- 否则输出所有整数解中 \(x\) 的最小正整数值、 \(y\) 的最小正整数值。
\(a, b, c \le 10^9\)
对于不定方程:
若 \(\gcd(a, b) \nmid c\) 则无解,否则可以用 exgcd 求出不定方程 \(ax + by = \gcd(a,b)\) 的一组整数解,记为 \(\begin{cases} x = x_0 \\ y = y_0 \end{cases}\) ,由此可以得到该方程的一特解:
考虑构造原方程整数通解形式,即:
需要保证 \(kb, ka\) 均为整数。令当 \(k\) 取到最小可能的正值时的 \(k_x = kb, k_y = ka\) ,那么有通解形式:
其中 \(s\) 为任意整数。
#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
ll exgcd(ll a, ll b, ll &x, ll &y) {
if (b) {
ll g = exgcd(b, a % b, y, x);
return y -= a / b * x, g;
} else
return x = 1, y = 0, a;
}
signed main() {
int T;
scanf("%d", &T);
while (T--) {
ll a, b, c;
scanf("%lld%lld%lld", &a, &b, &c);
ll x, y, g = exgcd(a, b, x, y);
if (c % g)
puts("-1");
else {
x *= c / g, y *= c / g;
ll p = b / g, q = a / g, k;
if (x < 0)
k = ceil((1.0 - x) / p), x += p * k, y -= q * k;
else
k = (x - 1) / p, x -= p * k, y += q * k;
if (y > 0) {
printf("%lld %lld %lld %lld %lld\n", (y - 1) / q + 1, x, (y - 1) % q + 1, x + (y - 1) / q * p, y);
} else
printf("%lld %lld\n", x, y + q * (ll)ceil((1.0 - y) / q));
}
}
return 0;
}
类欧几里得算法
给定 \(n, a, b, c\) ,求:
\[\begin{aligned} f(a, b, c, n) &= \sum_{i = 0}^n \lfloor \frac{ai + b}{c} \rfloor \\ g(a, b, c, n) &= \sum_{i = 0}^n \lfloor \frac{ai + b}{c} \rfloor i \\ h(a, b, c, n) &= \sum_{x = 0}^n \lfloor \frac{a x + b}{c} \rfloor^2 \end{aligned} \]\(n, a, b, c \le 10^9\)
\(f(a, b, c, n)\)
考虑先将 \(a \ge c\) 或 \(b \ge c\) 的情况转化为 \(a, b < c\) 的情况:
下面考虑 \(a, b < c\) 的情况,记 \(m = \lfloor \frac{an + b}{c} \rfloor\) ,则:
不难发现这里把 \(a\) 和 \(c\) 的位置交换了,这个过程类似于辗转相除法,因此时间复杂度为 \(O(\log V)\) 。
\(g(a, b, c, n)\)
同样考虑先将 \(a \ge c\) 或 \(b \ge c\) 的情况转化为 \(a, b < c\) 的情况,类似可以得出:
下面考虑 \(a, b < c\) 的情况,记 \(m = \lfloor \frac{an + b}{c} \rfloor, t = \lfloor \frac{cj + c - b - 1}{a} \rfloor\) ,则:
\(h(a, b, c, n)\)
同样考虑先将 \(a \ge c\) 或 \(b \ge c\) 的情况转化为 \(a, b < c\) 的情况,类似可以得出:
下面考虑 \(a, b < c\) 的情况,记 \(m = \lfloor \frac{an + b}{c} \rfloor, t = \lfloor \frac{cj + c - b - 1}{a} \rfloor\) 。为了避免出现求和号相乘的情况,考虑将平方化为:
则:
化简前面一部分:
整理得到:
实现
#include <bits/stdc++.h>
using namespace std;
const int Mod = 998244353, inv2 = (Mod + 1) / 2, inv6 = 166374059;
struct Node {
int f, g, h;
};
inline int add(int x, int y) {
x += y;
if (x >= Mod)
x -= Mod;
return x;
}
inline int dec(int x, int y) {
x -= y;
if (x < 0)
x += Mod;
return x;
}
inline Node calc(int a, int b, int c, int n) {
int s1 = 1ll * n * (n + 1) % Mod * inv2 % Mod,
s2 = 1ll * n * (n + 1) % Mod * (n * 2 + 1) % Mod * inv6 % Mod;
if (!a) {
Node ans;
ans.f = 1ll * (n + 1) * (b / c) % Mod;
ans.g = 1ll * s1 * (b / c) % Mod;
ans.h = 1ll * (n + 1) * (b / c) % Mod * (b / c) % Mod;
return ans;
}
if (a >= c || b >= c) {
Node res = calc(a % c, b % c, c, n), ans;
ans.f = add(add(1ll * (a / c) * s1 % Mod, 1ll * (b / c) * (n + 1) % Mod), res.f);
ans.g = add(add(1ll * s2 * (a / c) % Mod, 1ll * s1 * (b / c) % Mod), res.g);
ans.h = add(add(add(res.h, 2ll * (b / c) * res.f % Mod), 2ll * (a / c) * res.g % Mod),
add(1ll * (a / c) * (a / c) % Mod * s2 % Mod,
add(1ll * (b / c) * (b / c) % Mod * (n + 1) % Mod,
1ll * (a / c) * (b / c) % Mod * n % Mod * (n + 1) % Mod)));
return ans;
}
int m = (1ll * a * n + b) / c;
Node res = calc(c, c - b - 1, a, m - 1), ans;
ans.f = dec(1ll * n * m % Mod, res.f);
ans.g = 1ll * dec(dec(1ll * m * n % Mod * (n + 1) % Mod, res.h), res.f) * inv2 % Mod;
ans.h = dec(dec(dec(1ll * n * m % Mod * (m + 1) % Mod, 2ll * res.g % Mod), 2ll * res.f % Mod), ans.f);
return ans;
}
signed main() {
int T;
scanf("%d", &T);
while (T--) {
int n, a, b, c;
scanf("%d%d%d%d", &n, &a, &b, &c);
Node ans = calc(a, b, c, n);
printf("%d %d %d\n", ans.f, ans.h, ans.g);
}
return 0;
}
线性同余方程组
线性同余方程组形如:
CRT
前置条件:\(m\) 两两互质。
先求出 \(M = \prod_{i = 1}^k m_i\) ,对于第 \(i\) 个方程,求出:
- \(p_i = \frac{M}{m_i}\) 。
- \(p_i\) 在模 \(m_i\) 意义下的逆元 \(p_i^{-1}\) 。
- \(c_i = p_i p_i^{-1} \bmod M\) 。
方程组在模 \(M\) 意义下的唯一解为 \(x = \sum_{i = 1}^k a_i c_i \pmod{M}\) 。
证明:只需证明上述算法解出的 \(x\) 均满足每一个方程即可。
当 \(i \not = j\) 时,显然有 \(p_j \equiv 0 \pmod{m_i}\) ,故 \(c_j \equiv p_j \equiv 0 \pmod{m_i}\) ,又因为 \(c_i \equiv p_i (p_i^{-1} \bmod m_i) \equiv 1 \pmod{m_i}\) ,所以有:
\[\begin{aligned} x &\equiv \sum_{j = 1}^k a_j c_j &\pmod{m_i} \\ &\equiv a_i c_i &\pmod{m_i} \\ &\equiv a_i p_i (p_i^{-1} \bmod m_i) &\pmod{m_i} \\ &\equiv a_i &\pmod{m_i} \end{aligned} \]因此上述算法解出的 \(x\) 均满足每一个方程。
inline ll CRT() {
ll M = 1;
for (int i = 1; i <= n; ++i)
M *= m[i];
ll res = 0;
for (int i = 1; i <= n; ++i)
res = (res + mul(mul(a[i], M / m[i], M), inv(M / m[i], m[i]), M)) % M;
return res;
}
exCRT
无前置条件限制。
考虑将两个同余方程合并为一个同余方程,设两个方程分别为:
转化为不定方程:
其中 \(p, q \in \mathbb{Z}\) ,则:
由裴蜀定理,当 \(\gcd(m_1, m_2) \nmid (a_2 - a_1)\) 时方程无解,否则用 exgcd 求出一组可行解 \(p, q\) ,则 \(x \equiv m_1 p + a_1 \pmod{\operatorname{lcm}(m_1, m_2)}\) 。
推广到多个方程,类似地按上述过程两两合并即可。
inline pair<ll, ll> merge(pair<ll, ll> a, pair<ll, ll> b) {
ll a1 = a.first, m1 = a.second, a2 = b.first, m2 = b.second;
ll x, y, g = exgcd(m1, m2, x, y), l = m1 / g * m2, c = ((a2 - a1) % l + l) % l;
if (c % g)
return make_pair(-1ll, -1ll);
return make_pair((a1 + mul(mul(x, c / g, l), m1, l)) % l, l);
}
阶与原根
若满足同余式 \(a^n \equiv 1 \pmod{m}\) 的最小正整数 \(n\) 存在,则称 \(n\) 为 \(a\) 模 \(m\) 的阶,记作 \(\delta_m(a)\) 。
阶的性质:
- \(a, a^2, \cdots, a^{\delta_m(a)}\) 模 \(m\) 两两不同余。
- 若 \(a^n \equiv 1 \pmod{m}\) ,则 \(\delta_m(a) \mid n\) 。
- 推论:若 \(a^p \equiv a^q \pmod{m}\) ,则 \(p \equiv q \pmod{\delta_m(a)}\) 。
- 设 \(\gcd(a, m) = \gcd(b, m) = 1\) ,则 \(\delta_m(a, b) = \delta_m(a) \delta_m(b)\) 的充要条件是 \(\gcd(\delta_m(a), \delta_m(b)) = 1\) 。
- 若 \(\gcd(a, m) = 1\) ,则 \(\delta_m(a^k) = \frac{\delta_m(a)}{\gcd(\delta_m(a), k)}\) 。
若 \(\gcd(g, m) = 1\) 且 \(\delta_m(g) = \varphi(m)\) ,则称 \(g\) 为模 \(m\) 的原根。
原根的性质:
- 原根存在定理:\(m\) 存在原根当且仅当 \(m = 2, 4, p^\alpha, 2p^\alpha\) ,其中 \(p\) 为奇素数。
- 原根个数:若 \(m\) 有原根,则数量为 \(\varphi(\varphi(m))\) 。
- 原根判定定理:设 \(m \ge 3\) 且 \(\gcd(g, m) = 1\) ,则 \(g\) 是模 \(m\) 的原根的充要条件是 \(\varphi(m)\) 的每个质因子 \(p\) 均满足 \(g^{\frac{\varphi(m)}{p}} \not \equiv 1 \pmod{m}\) 。
- 最小原根范围估计:\(p\) 为素数时,\(g_p = \Omega(\log p)\) 。
离散对数问题
离散对数方程:
其中 \(x\) 即为模 \(p\) 意义下的 \(\log_a b\) 。
BSGS
P3846 [TJOI2007] 可爱的质数/【模板】BSGS
前置条件:\(a \bot p\) 。
令 \(x=A \times \lceil \sqrt{p} \rceil - B\) ,其中 \(0 \le A,B \le \lceil \sqrt{p} \rceil\) ,则:
考虑记录 \(b \times a^B\) 的所有取值,再枚举所有的 \(A\) ,寻找满足等式的 \(B\) 是否存在即可,时间复杂度 \(O(\sqrt{p})\) 。
inline int BSGS(int a, int b, int p) {
a %= p, b %= p;
map<int, int> mp;
int t = 1, L = ceil(sqrt(p));
mp[b] = 0;
for (int i = 1; i <= L; ++i)
t = 1ll * t * a % p, mp[1ll * b * t % p] = i;
for (int i = 1, u = t; i <= L; ++i, u = 1ll * u * t % p)
if (mp.find(u) != mp.end())
return 1ll * i * L - mp[u];
return -1;
}
exBSGS
无前置条件。
记 \(g = \gcd(a, p)\) ,若 \(g \nmid b\) 则原方程无解。
证明:若方程有解,不妨设 \(a '= \frac{a}{g}, p' = \frac{p}{g}\) ,则 \(a = a'g, p = p'g\) 。带入方程得到 \((a'g)^x \equiv b \pmod{p'g}\) ,转化为不定方程 \(a'^x g^x + y p'g = b\) ,即 \(a'x g^{x - 1} + yp' = \frac{b}{g}\) 。由于 \(a'xg^{x-1} + yp'\) 为整数,因此 \(g \mid b\) 。
将 \(g\) 带入原方程,得:
此时 \(a, \frac{p}{g}\) 仍有可能不互质,不断进行上述操作直到 \(a \bot \frac{p}{g}\) 。
记 \(G = \prod g\) ,\(k\) 为化简次数,带入原方程可得:
由于 \(a \bot \frac{p}{G}\) ,因此 \(\frac{a^k}{G} \bot \frac{p}{G}\) ,从而可以将 \(\frac{a^k}{G}\) 扔到方程右边,之后用普通 BSGS 解决即可。
注意这里没有排除解 \(\le k\) 的情况,需要额外枚举判断。
inline int exBSGS(int a, int b, int p) {
a %= p, b %= p;
if (b == 1 || p == 1)
return 0;
int g = __gcd(a, p), k = 0, D = 1;
while (g > 1) {
if (b % g)
return -1;
++k, b /= g, p /= g, D = 1ll * D * (a / g) % p;
if (D == b)
return k;
g = __gcd(a, p);
}
int f = BSGS(a, 1ll * b * inv(D, p) % p, p);
return ~f ? f + k : -1;
}
二次剩余
二次剩余方程形如:
下面讨论 \(p\) 为奇素数且 \(n \ne 0\) 的情况,不难发现任意一对相反数都对应一个二次剩余,因此方程在 \([0, p - 1]\) 范围内有两个解或无解。
欧拉准则
欧拉准则用于快速判断一个数是不是二次剩余,即上述方程是否有解。
由费马小定理 \(n^{p - 1} \equiv p \pmod{p}\) ,得到 \(n^{2(\frac{p - 1}{2})} - 1 \equiv 0 \pmod{p}\) ,即 \(n^{\frac{p - 1}{2}} = \pm 1\) 。
若 \(n\) 是二次剩余,则 \(n^{\frac{p - 1}{2}} \equiv (x^2)^{\frac{p - 1}{2}} \equiv x^{p - 1} \equiv 1 \pmod{p}\) 。
若 \(n^{\frac{p - 1}{2}} \equiv 1 \pmod{p}\) ,设 \(n = g^k\) ,其中 \(g\) 为 \(p\) 的原根,则 \(g^{\frac{k (p - 1)}{2}} \equiv 1 \pmod{p}\) ,由于 \(g\) 是原根,则必有 \(p - 1 | \frac{k (p - 1)}{2}\) ,即 \(k\) 一定是偶数,那么 \(x = g^{\frac{k}{2}}\) 即是 \(n\) 开根的结果,因此 \(n\) 是二次剩余。
因此得到结论:
- \(n^{\frac{p - 1}{2}} \equiv 1 \pmod{p}\) 是 \(n\) 是二次剩余的充要条件。
- \(n^{\frac{p - 1}{2}} \equiv -1 \pmod{p}\) 是 \(n\) 是非二次剩余的充要条件。
Cipolla 算法
先找到一个 \(a\) 满足 \(a^2 - n\) 是非二次剩余,由于非二次剩余的数量接近一半,可以通过随机后检验的方式期望 \(2\) 次找到一个 \(a\) 。
接下来类比实数域到复数域的推广,定义 \(i^2 \equiv a^2 - n \pmod{p}\) ,然后将所有数表示为 \(A + Bi\) 的形式。
引理一 :\(i^p \equiv -i \pmod{p}\)
证明:\(i^p \equiv i(i^2)^{\frac{p - 1}{2}} \equiv i(a^2 - n)^{\frac{p - 1}{2}} \equiv -i \pmod{p}\) 。
引理二:\((A + B)^p \equiv A^p + B^p \pmod{p}\)
证明:二项式定理展开后,由于 \(p\) 是质数,除了 \(\binom{p}{0}, \binom{p}{p}\) 外的组合数分子上的阶乘无法消除,模 \(p\) 都为 \(0\) ,故剩下 \(\binom{p}{0} A^0 B^p + \binom{p}{p} A^p B^0 = A^p + B^p\) 。
定理 : \((a + i)^{p + 1} \equiv n\)
证明:\((a + i)^{p + 1} \equiv (a^p + i^p)(a + i) \equiv (a + i)(a - i) \equiv a^2 - i^2 \equiv n \pmod{p}\) 。
那么 \((a + i)^{\frac{p + 1}{2}}\) 即是一个解,其相反数是另一个解。接下来只需证明 \((a + i)^{\frac{p + 1}{2}}\) 虚部为 \(0\) 。
证明:假设存在 \((A + Bi)^2 \equiv n \pmod{p}\) ,则 \(A^2 + B^2 i^2 + 2ABi \equiv n \pmod{p}\) ,即 \(A^2 + B^2 (a^2 - n) - n \equiv -2ABi \pmod{p}\) 。
式子左边虚部为 \(0\) ,故右边虚部也为 \(0\) ,即 \(AB \equiv 0 \pmod{p}\) 。
假设 \(B \not = 0\) ,则 \(A = 0\) ,也就是说 \((Bi)^2 \equiv n \pmod{p}\) ,即 \(i^2 \equiv n B^{-2} \pmod{p}\) 。
由于 \(B^2\) 是个二次剩余,其逆元 \(B^{-2}\) 一定也是个二次剩余,乘上二次剩余 \(n\) 后仍为二次剩余,这与 \(i^2\) 是非二次剩余矛盾,故假设不成立,即 \(B = 0\) 。
#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
mt19937 myrand(time(0));
ll n, p, i2;
struct Node {
ll a, b;
inline Node(const ll _a = 0, const ll _b = 0) : a(_a), b(_b) {}
inline Node operator * (const Node &rhs) const {
return Node(((a * rhs.a % p + i2 * b % p * rhs.b % p) % p + p) % p,
((a * rhs.b % p + b * rhs.a % p) % p + p) % p);
}
};
inline Node mi(Node a, ll b) {
Node res(1);
for (; b; b >>= 1, a = a * a)
if (b & 1)
res = res * a;
return res.a;
}
inline ll mi(ll a, ll b, ll p){
ll res = 1;
for (; b; b >>= 1, a = a * a % p)
if (b & 1)
res = res * a % p;
return res;
}
inline ll Cipolla(ll n, ll p) {
n %= p;
if (mi(n, (p - 1) / 2, p) == p - 1)
return -1;
ll a;
for (;;) {
a = myrand() % p;
i2 = ((a * a % p - n) % p + p) % p;
if (mi(i2, (p - 1) / 2, p) == p - 1)
break;
}
Node x(a, 1);
return mi(x, (p + 1) / 2).a;
}
signed main() {
int T;
scanf("%d", &T);
while (T--) {
scanf("%lld%lld", &n, &p);
if (!n) {
puts("0");
continue;
}
ll x1 = Cipolla(n, p), x2 = p - x1;
if (x1 == -1)
puts("Hola!");
else {
if (x1 == x2)
printf("%lld\n", x1);
else
printf("%lld %lld\n", min(x1, x2), max(x1, x2));
}
}
return 0;
}