拓展欧几里得 到 乘法逆元 (+线性求法) 个人解释 + 普通模板

话说PION初赛 (本月13号) 要到了我还是得弄一下不然连50分都没有...........

 

首先我们要知道欧几里得是什么——某求最小公约数 (gcd) 的东西 这个东西如果不知道......那我真没办法

好了那拓展欧几里得是什么呢?就是 extendgcd 俗称 exgcd

好吧其实名称是没luan用的 我们来看看这东西的用处

然而只是用来求 ax + by = gcd(a,b) 的?当然 但是有许多其他式子 (我也没找几个) 如 ax ≡ 1 (mod b) 就可以转化为那个

好吧这就是我这篇要讲的题 链接丢这里先 后面再放一个

ax ≡ 1 (mod b) 转化一下就是 ax mod b ≡ 1

仔细想想 ax mod b 就是 ax ÷ b 取余

也就是 ax - b * (ax / b ( / 用来取整) ) = 1 然而这样并不好求 我们设和 b 乘起来的一大坨东西为 y 则有

ax - by = 1 也就是 ax + (-by) = 1 诶这里 y 是个负数耶 然而并不关我们的事 谁说负数就求不了了呢?注意了 这里有伏笔

我们已经转化到了 ax + by 的形式 所以原式就是要 ax + by = 1 那我们就可以很容易发现

gcd(x,y) = 1 !!!没错!!!我们的拓展欧几里得可以派上用场了

好吧拓展欧几里得是什么都没说呢 现在开始讲解就是一个式子啦要讲什么嘛 我直接推导然后应用就好了 (其实要讲的就是这些 讲完以后就懂了) 诸客官请看下面

欧几里得是干嘛的?辗转相除 (好像听说过相减不过这边我们不管) 嘛

拓展欧几里得呢?肯定也是一样的尿性嘛 用辗转相除 有什么用呢 啊哈

因为这里 gcd = 1 所以 x 和 y 的最大公约数为 1 通过普通欧几里得可以发现 此时两数 1 个是 1 另一个是 0

然后拓展欧几里得里如果知道了这些 那我们就少了两个未知数 然后这题 a 和 b 又知道 那完全就没有未知数了嘛

然后我们回溯就好了呀~~ 好吧证明一下可行性

首先

ax   +    by    = gcd(a,b)
ax'  + (b % a)y'= gcd(a,b % a)

显然 gcd(a,b) = gcd(a,b % a)
所以 ax + by = ax' + (b % a)y'
那我们根据欧几里得最后就得到了 x = 1,y = 0

于是次数方程变成 1 * a + 0 * b = gcd(a,b) = a
这推到底的解就出来了

那我们考虑推回去 因为上一层的 a = a,b = b % a
哦不 欧几里得的辗转相除每层的 a 和 b 都是反过来的
因为两个数的大小问题 b > a 但是 b % a < a 要反过来
所以应该是 b = a,a = b % a

但是我们要求的是 x 和用来辅助的 y
记得有个叫乘法交换律的东西吧
那我们就不变 a 和 b 变 x 和 y 像推 a 和 b 一样
然后利用每层递归的 a 和 b 的值 就可以找到原来的 x 和 y 了
但原来的那个是一个特解 如果像本题一样找最小的 就要像下面代码一样
如果找第 k 小的 就用最小解乘上 k 就好啦

下放代码 隔开方便复制 题目链接戳这里

#include <cstdio>
using namespace std;
int x,y;
void exgcd(int a,int b)
{
	if (!b) {x = 1,y = 0; return;}
	exgcd(b,a % b);
	int bot = x;
	x = y;
	y = bot - a / b * y;
}
int main()
{
	int a,b;
	scanf("%d%d",&a,&b);
	exgcd(a,b);
	printf("%d\n",(x % b + b) % b);
	return 0;
}
/*Frocean用 a b 只做循环变量~~
#include <cstdio>
using namespace std;
int i,j;
void exgcd(int x,int y)
{
	if (!y) {i = 1,j = 0; return;}
	exgcd(y,x % y);
	int bot = i;
	i = j;
	j = bot - x / y * j;
}
int main()
{
	int x,y;
	scanf("%d%d",&x,&y);
	exgcd(x,y);
	printf("%d\n",(i % y + y) % y);
	return 0;
}
*/

 

拓展欧几里得的这种用法 又可以说是求乘法逆元 不过乘法逆元的定义可能和上面那式子不太一样

若 a * x \equiv 1 (mod b) 且 a 与 b 互质,那么 x 为 a 的逆元 x 也可表示为 a^{-1} 也称 x 为 a 的倒数

( 后面讲的时候 b 换成 p 因为题目原因 )

就像上面那个求就好了 但注意一点

    比如说 4 * 3 在 mod 11 的意义下 3 是 4 的乘法逆元
    然而 4 * 14 在 mod 11 的 意义下 14 也是 4 的乘法逆元
    但是 14 在 mod 11 意义下 就是 3 因为 14 = 11 + 3
    所以如果要求乘法逆元 输出的只有一个

好了好了 就像上面那程序一样就求得出来了 完全一样

但如果我们要求线性的解呢?比如这道题目 像上面那样一个个跑绝对会炸掉的

那我们就需要线性算法 这个要推一个式子

 

首先 1 的乘法逆元肯定要先弄出来 很显然 1^{-1} = 1 了 也就是 1^{-1} \equiv 1 (mod p) 且当 p>1 时

还有一个前提条件 线性算法只能求 p 为质数时 1 ~ p - 1 的乘法逆元

因为这个条件 我们设 p 是由一堆数构成的 因为 p 又是质数 所以要分解成 k 倍的 i + r 的形式

即 p = k * i + r,r < i,1 < i < p 其中 r < i 是因为 r ≥ i 时 r 可以化成 i 的 倍数

指定 i 为当前需要求的数 则其乘法逆元为 i^{-1}

我们可由 p mod p = 0 得到一个式子

 k * i + r \equiv 0 (mod p)

接下来转化 同乘 i^{-1}r^{-1} (就是同除ir) 得

 k * r^{-1} + i^{-1} \equiv 0 (mod p)

i^{-1} \equiv - k * r^{-1} (mod p)

此时将右边两项分开 k = p / i 并且 r = p mod i 此时指数不变 见下

i^{-1} \equiv - \left \lfloor \frac{p}{i} \right \rfloor * (p mod i)^{-1} (mod p)

此时 (p mod i)^{-1} 是 p mod i 这个数的逆元 这个数必定小于 i 因此我们早已求出

这时候我们可以得到第一种式子

ans [ i ] = - p / a * ans [ p % a ] % p

然而这个式子是错的 因为会有负数 所以要改成下面那样 原理和上面拓展欧几里得的代码一样

ans [ i ] = ( ( - p / a * ans [ p % a ] ) % p + p) % p

然后这样就可以跑了 实测556ms(当然要开long long)

下放代码

#include <cstdio>
#define ll long long
const int MAXN = 3000010;
int ans[MAXN],n,p;
int main()
{
    ans[1] = 1; scanf("%d%d",&n,&p);
    for (int i = 2 ; i <= n ; ++ i)
		ans[i] = ((- (ll)p / i * ans[p % i]) % p + p) % p;
    for (int i = 1 ; i <= n ; ++ i)
		printf("%d\n",ans[i]);
    return 0;
}

 

还有 其实我们可以继续推下去

看一下我们之前的 - \left \lfloor \frac{p}{i} \right \rfloor 通俗一点就是 - \frac{p}{i} 看好了 在 mod p 意义下 这个项和下面那个项相等——

p - \frac{p}{i} (mod p)

为什么呢?

因为我们可以先把前面的 p mod p 就是 0 那下面那个式子就变成 - \frac{p}{i} 了

众所周知 除 比 取模 要快上一些

于是我们可以得出下面这个式子

ans [ i ] = ( p - p / i ) * ans [ p % a ] % p

于是我们拿下面这个式子跑 实测470ms(当然要开long long)

下放代码

#include <cstdio>
#define ll long long
const int MAXN = 3000010;
int ans[MAXN];
int main()
{
	int n,p;
	ans[1] = 1;
	scanf("%d%d",&n,&p);
	for (int i = 2 ; i <= n ; ++ i)
		ans[i] = (ll)(p - p / i) * ans[p % i] % p;
	for (int i = 1 ; i <= n ; ++ i)
		printf("%d\n",ans[i]);
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值