动态线条
动态线条end

数论进阶 

 数论进阶 

扩展欧几里得算法

裴蜀定理(Bézout's identity)

1 :对于任意整数 ab ,存在一对整数 xy ,满足 ax+by=GCD(a,b)

2:对于任意整数 ab ,二元一次不定方程 ax+by=c 有整数解 (x,y) 当且仅当 GCD(a,b)|c

扩展欧几里得算法(Extended Euclidean algorithm)

首先,我们来回顾一下欧几里得算法:

void gcd(int a,int b){
	return b==0?a:gcd(b,a%b);
}

扩展欧几里得算法是用于在已知 ab 的情况下求一组解 xy ,使他们之间满足:ax+by=GCD(a,b)=d。(GCD即最大公约数)。我们要计算的是 ab 的最大公约数,并求出 xy 使得 ax+by=d

此时,我们已经计算出了下一个状态:b(a%b) 的最大公约数,并求出了一组 x1,y1 使得:bx1+(a%b)y1=d 。而相邻状态之间的关系是怎样的呢?

我们知道:a%b=aabb ,所以有:

(1)d=bx1+[aabb]y1(2)=bx1+ay1abby1(3)=ay1+b(x1aby1)

所以:x=y1,y=x1aby1

特别的:在欧几里得算法执行到最后一步,即 b=0 时,我们可以得到一对整数 x=1,y=0 ,使得 a1+00=GCD(a,0)

由此便可以写出扩展欧几里得的模板(exGCD),代码如下:

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

线性同余方程

乘法逆元

中国剩余定理

扩展中国剩余定理

原根

BSGS

代码如下:

#include<bits/stdc++.h>
#include<ext/pb_ds/assoc_container.hpp>
#include<ext/pb_ds/hash_policy.hpp>
using namespace std;
using namespace __gnu_pbds;
typedef long long ll;
int log_mod(ll a,ll b,ll n) {
	if (b==1) return 0;
	gp_hash_table<int,int> hash;
	int t=ceil(sqrt(n));
	ll z=1;
	for (int i=0; i<t; i++) {
		hash[b*z%n]=i;
		z=z*a%n;
	}
	int now=1;
	for (int i=1; i<=t; i++) {
		now=now*z%n;
		if (hash.find(now)!=hash.end()) {
			return i*t-hash[now];
		}
	}
	return -1;
}

高次剩余

很遗憾,这个内容作者在OIWIKI上找到,而百度百科上的内容也不全。

为了学习高次剩余部分的内容,我们先给出一个问题:

求解 xab(mod p)

我们规定:p 是个质数。

在学习了原根方面的知识后,我们可以得知:p一定存在一个原根,我们假设这个原根为 g 。因此,对于模 p 意义下的任意的数 x(0x<p) 有且仅有一个数满足 i(0i<p1) 满足 x=gi

接下来将会列出解决方案。

方案一

我们令 x=gc ,由于 gp 的原根(而这个 gc ,通过之前学习的方法,我们一定可以找到),那么问题将转化为求解 (gc)ab(modp) ,我们可以在将其变换,得到:

(ga)cb(mod p)

于是,这就变成了离散对数的基本模型了,我们可以用 BSGS 解决这道题,只要在 O(p) 内解出 c ,这样就可以得到原方程的一个特解 x0gc(mod p)

方案二

仍然令 x=gc ,此时,我们在规定 b=gt ,于是我们得到:

gacgt(mod p)

方程两边同时取离散对数后得到:

act(mod φ(p))

而方程中的 t 可以通过 BSGS 求解 gtb(mod p) 得到,于是这就转化成了一个线性同余方程的问题。这样便可解除一个特解 c0 ,c 的所有通解为 c0+kφ(p)gcd(a,φ(p)) ,其中 k 为整数。

于是 x 的通解为: gc0+kφ(p)gcd(a,φ(p)) ,而我们要求的是所有在 [0,p1] 范围内的解,可以将解一个个放进 vector 中,直到重复(可以用 set 或者 hash 来判断重复,这里我们采用的是 hash 的方法,不了解 hash 的读者可以看这里,但我们的 hash 是直接调用的,而不是手写的)

代码如下:

#include<bits/stdc++.h>
#include<ext/pb_ds/assoc_container.hpp>
#include<ext/pb_ds/hash_policy.hpp>
using namespace std;
using namespace __gnu_pbds;
typedef long long ll;
vector<int> extract_mod(int a,int b,int p){
	vector<int> res;
    int g=find_yg(p);
    int c=log_mod(pow_mod(g,a,p),b,p);
    if (c==-1) return res;
    int x=pow_mod(g,c,p),t=pow_mod(g,(p-1)/gcd(a,p-1),p);
    gp_hash_table<int,int> hash;
    while(1){
    	res.push_back(x);
    	hash[x]=1;
    	x=(ll)x*t%p;
    	if (hash[x]==1) break;
	}
	return res;
}

exBSGS

代码如下:

#include<bits/stdc++.h>
#include<ext/pb_ds/assoc_container.hpp>
#include<ext/pb_ds/hash_policy.hpp>
using namespace std;
using namespace __gnu_pbds;
typedef long long ll;
int exgcd(int a,int b,int &x,int &y){
	if (b==0){
		x=1;
		y=0;
		return a;
	}
	int d=exgcd(b,a%b,x,y);
	int t=x;
	x=y;
	y=t-a/b*y;
	return d;
}
int exBGSG(int a,int b,int n){
	if (b==1) return 0;
	int cnt=0,x,y;
	ll w=1;
	while(1){
		int d=exgcd(a,n,x,y);
		if (d==1) break;
		if (b%d!=0) return -1;
		b/=d;
		n/=d;
		w=w*a/d%n;
		cnt++;
		if (b==w) return cnt;
	}
	exgcd(w,n,x,y);
	b=(ll)b*(x%n+n)%n;
	a=a%n;
	gp_hash_table<int,int> hash;
	int t=ceil(sqrt(n));
	ll z=1;
	for (int i=0;i<t;i++){
		hash[b*z%n]=i;
		z=z*a%n;
	}
	int now=1;
	for (int i=1;i<=t;i++){
		now=now*z%n;
		if (hash.find(now)!=hash.end()){
			return i*t-hash[now]+cnt;
		}
	}
	return -1;
}
int main(){
	
	return 0;
}
posted @   冘木  阅读(217)  评论(0编辑  收藏  举报
编辑推荐:
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
Live2D
欢迎阅读『数论进阶 』
动态线条
动态线条end
点击右上角即可分享
微信分享提示