乘法逆元 [数学]

定义

逆元素是指一个可以取消另一给定元素运算的元素
—百度百科

简单说 就是 a a 1 = 1 a * a^{-1} = 1 aa1=1 一样 在模 意义下有更多性质

常见求逆元的方法

1.拓展欧几里得求逆元

ps必须 a , p a,p a,p互质


复杂度 l o g p log p logp

#include <bits/stdc++.h>
using namespace std;
const int maxn = 3e6 + 10;
long long n, m;

long long exgcd(long long a, long long b, long long &x, long long &y) {
	if (b == 0) {
		x = 1, y = 0;
		return a;
	}
	int d = exgcd(b, a % b, y, x);
	y -= a / b * x;
	return d;
}

signed main() {
	ios::sync_with_stdio(0);
	long long a, b;
	cin >> a >> b;
	long long x = 0, y = 0;
	exgcd(a, b, x, y);
	cout << (x % b + b) % b << endl;
	return 0;
}

2.欧拉定理求逆元

也是 a p 必须互质

欧拉值 求法只要按照 公式直接出就好

复杂度 l o g ( p h i ( p ) ) + s q r t ( n ) log(phi(p)) + sqrt(n) log(phi(p))+sqrt(n)

#include <bits/stdc++.h>
using namespace std;
const int maxn = 3e6 + 10;
long long n, m;

long long pow_mod(long long a, long long b, long long p) {
	long long res = 1;
	for(; b; b >>= 1) {
		if(b & 1) res = res * a % p;
		a = a * a % p;
	}
	return res;
}

long long phi(long long x, long long p) {
	long long res = x, tmp = x;
	for(int i = 2; i * i <= tmp; i ++) {
		if(x % i == 0) {
			res = (res / i) % p * ((i - 1 % p)) % p;
			while(x % i == 0) x /= i;
		}
	}
	if(x > 1) res = (res / x) % p * ((x - 1) % p) % p;
	return res;
} // 一般欧拉值 也不太可能比p大 板子就写下吧 一般也不需要 

long long phi(long long x) {
	long long res = x, tmp = x;
	for(int i = 2; i * i <= tmp; 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;
}
signed main(){
	cin >> a >> b;
	long long tmp = phi(b) - 1;
	cout << pow_mod(a, tmp, b) << endl; 
	return 0;
}

3.费马小定理求逆元

只要p 是质数 就行 不要求互质
#include <bits/stdc++.h>
using namespace std;
const int maxn = 2e5 + 10;
long long n, m;

long long pow_mod(long long a, long long b, long long p) {
	long long res = 1;
	for(; b; b >>= 1) {
		if(b & 1) res = res * a % p;
		a = a * a % p;
	}
	return res;
}

const int p = 19260817;
template<typename T>
inline void read(T &x) {
	x = 0;
	char ch = getchar();
	bool f = false;
	while(!isdigit(ch)) {
		f |= (ch == '-');
		ch = getchar();
	}
	while(isdigit(ch)) {
		x = (x << 1) + (x << 3) + (ch ^ 48);
		x %= p;
		ch = getchar();
	}
	x = f ? -x : x;
}

signed main() {
	long long a, b;
	read(a), read(b);
	if(!b) puts("Angry!");
	else cout << a * pow_mod(b, p - 2, p) % p << endl;
	return 0;
}

线性求逆元

也算是 打表 不过之后我们通过类似 阶乘逆元的方式 可以 看出 前缀积的逆元就是逆元的前缀积(之后细讲)

线性求逆元模板

#include <bits/stdc++.h>
using namespace std;
const int maxn = 3e6 + 10;
int n, m;

long long pow_mod(long long a, long long b, long long p) {
	long long res = 1;
	for(; b; b >>= 1) {
		if(b & 1) res = res * a % p;
		a = a * a % p;
	}
	return res;
}

int inv[maxn], p;

signed main() {
	scanf("%d %d", &n, &p);
	inv[1] = 1;
	cout << inv[1] << endl;
	for(int i = 2; i <= n; i ++) {
		inv[i] = 1ll * (p - p / i) * inv[p % i] % p;
		printf("%d\n", inv[i]);
	}
	return 0;
}

阶乘 O ( n ) O(n) O(n) 逆元

这里 证明 一并放到 乘法逆元2 中

#include <bits/stdc++.h>
using namespace std;
const int maxn = 3e6 + 10;
long long n, m;

long long pow_mod(long long a, long long b, long long p) {
	long long res = 1;
	for(; b; b >>= 1) {
		if(b & 1) res = res * a % p;
		a = a * a % p;
	}
	return res;
}

long long inv[maxn], p;
long long f[maxn], c[maxn];

signed main() {
	scanf("%lld %lld", &n, &p);
	c[1] = c[0] = 1;
	for(int i = 2; i <= n; i ++) 
		c[i] = c[i - 1] * i % p;
	f[n] = pow_mod(c[n], p - 2, p);
	
	for(int i = n - 1; i >= 1; i --) {
		f[i] = f[i + 1] * (i + 1) % p;
	}
	
	for(int i = 1; i <= n; i ++) 
		printf("%lld\n", f[i] * c[i - 1] % p);
	return 0;
}

乘法逆元2


注释部分T了 2333

#include <bits/stdc++.h>
using namespace std;
int p, n, k;
const int maxn = 5e6 + 10;

template<typename T>
inline void read(T &x) {
	x = 0;
	char ch = getchar();
	bool f = false;
	while(!isdigit(ch)) {
		f |= (ch == '-');
		ch = getchar();
	}
	while(isdigit(ch)) {
		x = (x << 1) + (x << 3) + (ch ^ 48);
		ch = getchar();
	}
	x = f ? -x : x;
}

long long pow_mod(long long a, long long b, long long p) {
	long long res = 1;
	for(; b; b >>= 1) {
		if(b & 1) res = res * a % p;
		a = a * a % p;
	}
	return res;
}

int b[maxn], s[maxn], a[maxn];

int main() {
	read(n), read(p), read(k);
	int ans = 0;
// for(int i = 1, a; i <= n; i ++) {
// read(a);
// ans = (1ll * ans + pow_mod(k, i, p) * pow_mod(a, p - 2, p)) % p ;
// }
// printf("%d\n", ans);
	s[0] = 1;
	b[n + 1] = 1;
	for(int i = 1; i <= n; i ++) {
		read(a[i]);
		s[i] = 1ll * s[i - 1] * a[i] % p;
	}
	b[n + 1] = pow_mod(s[n], p - 2, p);
	for(int i = n; i >= 1; i --) {
		b[i] = 1ll * b[i + 1] * a[i] % p;
	}
	int tmpk = k;
	for(int i = 1; i <= n; i ++) {
		ans = (ans + (1ll * b[i + 1] * s[i - 1]) % p * tmpk) % p;
		tmpk = 1ll * tmpk * k % p;
	}
	printf("%d\n", ans);
	return 0;
}
全部评论

相关推荐

bg双非本科,方向是嵌入式。这次秋招一共拿到了&nbsp;8&nbsp;个&nbsp;offer,最高年包&nbsp;40w,中间也有一段在海康的实习经历,还有几次国家级竞赛。写这篇不是想证明什么,只是想把自己走过的这条路,尽量讲清楚一点,给同样背景的人一个参考。一、我一开始也很迷茫刚决定走嵌入式的时候,其实并没有一个特别清晰的规划。网上的信息很零散,有人说一定要懂底层,有人说项目更重要,也有人建议直接转方向。很多时候都是在怀疑:1.自己这种背景到底有没有机会2.现在学的东西到底有没有用3.是不是已经开始晚了这些问题,我当时一个都没答案。二、现在回头看,我主要做对了这几件事第一,方向尽早确定,但不把自己锁死。我比较早就确定了嵌入式这个大方向,但具体做哪一块,是在项目、竞赛和实习中慢慢调整的,而不是一开始就给自己下结论。第二,用项目和竞赛去“证明能力”,而不是堆技术名词。我不会刻意追求学得多全面,而是确保自己参与的每个项目,都能讲清楚:我负责了什么、遇到了什么问题、最后是怎么解决的。第三,尽早接触真实的工程环境。在海康实习的那段时间,对我触动挺大的。我开始意识到,企业更看重的是代码结构、逻辑清晰度,以及你能不能把事情说清楚,而不只是会不会某个知识点。第四,把秋招当成一个需要长期迭代的过程。简历不是一次写完的,面试表现也不是一次就到位的。我会在每次面试后复盘哪些问题没答好,再针对性补。三、我踩过的一些坑现在看也挺典型的:1.一开始在底层细节上纠结太久,投入产出比不高2.做过项目,但前期不会总结,导致面试表达吃亏3.早期有点害怕面试,准备不充分就去投这些弯路走过之后,才慢慢找到节奏。四、给和我背景相似的人一点建议如果你也是双非,准备走嵌入式,我觉得有几件事挺重要的:1.不用等“准备得差不多了”再投2.项目一定要能讲清楚,而不是做完就算3.不要只盯着技术,多关注表达和逻辑很多时候,差的不是能力,而是呈现方式。五、写在最后这篇总结不是标准答案,只是我个人的一次复盘。后面我会陆续把自己在嵌入式学习、竞赛、实习和秋招中的一些真实经验拆开来讲,希望能对后来的人有点帮助。如果你正好也在这条路上,希望你能少走一点弯路。
点赞 评论 收藏
分享
萧索X:写篮球联赛干嘛,陪老板打篮球吗。还有实习经历要写自己所在岗位具体完成什么工作,自己的任务具体完成了什么需求,给公司带来了哪些量化增长
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务