简介
系统整理一下逆元相关的知识点,包括快速幂求逆元、拓展欧几里得求逆元、线性递推求逆元。
其中快速幂求逆元利用了费马小定理,它是欧拉定理的一个特殊情况。
首先逆元有几个性质:
-
唯一性。设存在 y1≡y2 同时为 x 模 p 的逆元,那么可以得到:
xy1x(y1−y2)≡xy2(modp)≡0(modp)
由于 x≡0,所以必然有 y1−y2≡0,矛盾。
-
互质性。即只有在 gcd(x,p)=1 的情况下存在 x 的逆元,如果不这样,那么把定义式用拓欧方程写出来:
xx−1+kp=1,k<0
由于 x−1,k 都为整数,那么:
gcd(x,p)∣(xx−1+kp)⇒gcd(x,p)∣1
这与假设条件矛盾。
模板 I
给定 n,p 求 1∼n 中所有整数在模 p 意义下的乘法逆元。
这里 a 模 p 的乘法逆元定义为 ax≡1(modp) 的解。
输入格式
一行两个正整数 n,p。
输出格式
输出 n 行,第 i 行表示 i 在模 p 下的乘法逆元。
数据范围
1≤n≤3×106,n<p<20000528,输入保证 p 为质数。
题目链接:P3811。
如果用快速幂求,那么复杂度是 O(nlogp),时限是 0.5s,故意卡了这个方法。
接下来就用到线性递推了,显然 inv(1)=1,然后考虑将 p 拆一下,令 p=ki+r,r∈[0,i),那么:
ki+rkr−1+i−1i−1≡0≡0≡−kr−1≡−⌊ip⌋(pmodi)−1
由于 r<i 因此可以递推。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| #include <bits/stdc++.h> using namespace std;
const int N = 3000010; int n, p, inv[N];
int main() { scanf("%d%d", &n, &p); inv[1] = 1; puts("1"); for (int i = 2; i <= n; i++) { inv[i] = 1LL * -p / i * inv[p%i] % p; if (inv[i] < 0) inv[i] += p; printf("%d\n", inv[i]); } return 0; }
|
模板 II
给定 n 个正整数 ai ,求它们在模 p 意义下的乘法逆元。
由于输出太多不好,所以将会给定常数 k,你要输出的答案为:
i=1∑naiki
答案对 p 取模。
输入格式
第一行三个正整数 n,p,k,意义如题目描述。
第二行 n 个正整数 ai,是你要求逆元的数。
输出格式
输出一行一个整数,表示答案。
数据范围
对于 100% 数据,1≤n≤5×106,2≤k<p≤109,1≤ai<p,保证 p 为质数。
题目链接:P5431。
如果暴力做的话复杂度是 O(nlogp) 还是被卡掉了,所以要线性。
通分一下式子,发现是:
∏i=1nai1i=1∑n(kiai∏j=1naj)=∏i=1nai1i=1∑n(kij=1∏i−1ajj=i+1∏naj)
所以求前缀后缀积即可,开个快读卡下常就过了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| #include <bits/stdc++.h> using namespace std;
const int N = 5000010; int n, p, k, a[N], pre[N], suf[N];
template<typename T> void read(T& res) { char ch = getchar(); T sig = 1; while (ch < '0' || ch > '9') { if (ch == '-') sig = -1; ch = getchar(); } res = 0; while ('0' <= ch && ch <= '9') { res = res*10 + (ch&15); ch = getchar(); } res *= sig; }
int qmi(int a, int k) { int res = 1; while (k) { if (k & 1) res = 1LL * res * a % p; a = 1LL * a * a % p; k >>= 1; } return res; }
int main() { read(n), read(p), read(k);
pre[0] = 1, suf[n+1] = 1; for (int i = 1; i <= n; i++) read(a[i]); for (int i = 1; i <= n; i++) pre[i] = 1LL * pre[i-1] * a[i] % p; for (int i = n; i >= 1; i--) suf[i] = 1LL * suf[i+1] * a[i] % p;
int prod = k, res = 0; for (int i = 1; i <= n; i++) { res = (res + 1LL * prod * pre[i-1] % p * suf[i+1]) % p; prod = 1LL * prod * k % p; } return !printf("%lld\n", 1LL * res * qmi(pre[n], p-2) % p); }
|
[NOIP2012] 同余方程
求关于 x 的同余方程 ax≡1(modb) 的最小正整数解。
输入格式
一行,包含两个整数 a,b,用一个空格隔开。
输出格式
一个整数 x0,即最小正整数解。输入数据保证一定有解。
数据范围
对于 100% 的数据,2≤a,b≤2×109。
题目链接:P1082。
不保证 b 为质数,那么没法用费马小定理,这里用拓展欧几里得算法求解。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| #include <bits/stdc++.h> using namespace std;
void exgcd(int a, int b, int& x, int &y) { if (b == 0) { x = 1, y = 0; return; } exgcd(b, a % b, y, x); y -= a/b * x; }
int main() { int a, b, x, y; scanf("%d%d", &a, &b); exgcd(a, b, x, y); x %= b; if (x < 0) x += b; return !printf("%d\n", x); }
|
数字变换
给定两个自然数 A,B,每次可以选择如下操作之一:
-
将 A 变成 A+11mod998244353。
-
将 A 变成 A×21mod998244353。
-
将 A 变成 A31mod998244353。
问至少需要多少次操作才能将 A 变成 B。可以证明一定有解。
首先考虑到这些操作得到的数并没有什么规律,看作较为随机的操作,所以如果单纯爆搜是 O(p) 的复杂度。那么结合生日悖论,我们设现在已经取了 k 个数字,并没有取到与之前相同的数字的概率是:
i=0∏k−1pp−i
计算出,当 k=4p 时,没有取到相同数字的概率已经降到了 0.03%,所以我们只需要 O(p) 次查找就一定能找到相同的数字,所以考虑双向搜索。
前两个的逆元都好说,第三个的逆就需要单独摘出来看看了。
首先 998244353 是质数,由欧拉定理可以得到:
ab≡abmodφ(p)(modp)
设知道 A31=a 那么 ak=A 就有:
a31k31k≡a(modp)≡1(modφ(p))
这个根不一定是唯一的,但是我们一定能通过这种方式构造出一个合法解,用 map
存储状态,复杂度 O(plogp) 可过。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| #include <bits/stdc++.h> using namespace std;
#define int long long const int P = 998244353;
int qmi(int a, int k) { int res = 1; while (k) { if (k & 1) res = res * a % P; a = a * a % P; k >>= 1; } return res; }
int bfs(queue<int>& q, map<int, int>& d, map<int, int>& d2, bool inv) { int t = q.front(); q.pop(); int dt = d[t]; if (d2.count(t)) return d2[t] + dt;
int to; if (inv) to = (t - 11 + P) % P; else to = (t + 11) % P; if (!d.count(to)) d[to] = dt + 1, q.push(to);
if (inv) to = (t * 617960790) % P; else to = (t * 21) % P; if (!d.count(to)) d[to] = dt + 1, q.push(to);
if (inv) to = qmi(t, 225410015); else to = qmi(t, 31); if (!d.count(to)) d[to] = dt + 1, q.push(to); return -1; }
int solve(int A, int B) { map<int, int> d1, d2; queue<int> q1, q2; d1[A] = 0, d2[B] = 0; q1.push(A), q2.push(B);
int res = -1, inv = 1; while (res == -1) { if (inv) res = bfs(q2, d2, d1, true); else res = bfs(q1, d1, d2, false); inv ^= 1; } return res; }
signed main() { int A, B; cin >> A >> B; cout << solve(A, B) << '\n';
return 0; }
|