Typesetting math: 100%

『高次同余方程 Baby Step Giant Step算法』

Parsnip·2019-04-10 21:20·756 次阅读

『高次同余方程 Baby Step Giant Step算法』

<更新提示>

<第一次更新>


<正文>

高次同余方程#

一般来说,高次同余方程分axb(mod p)xab(mod p)两种,其中后者的难度较大,本片博客仅将介绍第一类方程的解决方法。

给定a,b,p,其中gcd(a,p)=1,求方程axb(mod p)的最小非负整数解。

普通分析和朴素算法#

先介绍一下欧拉定理:

如果正整数ap互质,则aϕ(p)1(mod p)

注意到题中所给的条件符合欧拉定理的前提条件,所以我们得知aϕ(p)1(mod p),又因为ϕ(p)p1a01(mod p),所以在0p1范围内,模p意义下a的幂一定形成了至少一次循环。也就是说,我们只要在0p2范围内枚举解x,就一定能找到解,用快速幂判断即可求解该方程,时间复杂度O(plog2p)

Baby Step Giant Step算法#

对于求解该类高次同余方程,Baby Step Giant Step可以优化枚举算法,在O(p)的时间内求解该方程。

我们设解x=itj,其中0jt1,则方程为ait+jb(mod p)(at)ibaj(mod q)

对于所有的j[0,t1],将baj mod p的值插入hash表。

枚举i的所有可能取值[0,pt],计算出(at)i mod p的值,并在hash表中检索该值,如果该值存在,就可以用itj更新答案。

该算法可以保证枚举到x[0,p2]范围内的每一种情况,其时间复杂度为O(pt+p),该函数为经典的对勾函数,当参数tp时,其函数值最小,保证其时间复杂度为O(p)

以下给出求解方程最小非负整数解的程序,无解时返回1
Code:

Copy
inline long long Baby_Step_Giant_Step(long long a,long long b,long long p) { map < long long , long long > hash;hash.clear(); b%=p;long long t=(long long)sqrt(p)+1,mul=1; for (int j=0;j<t;j++) { hash[ mul * b % p ] = j; mul = mul * a % p; } if (a%p==0)return b==0 ? 1 : -1; a = 1; for (int i=0;i<=t;i++) { long long j = ( hash.find(a) != hash.end() ? hash[a] : -1 ); if (j>=0&&i*t-j>=0)return i*t-j; a = a * mul % p; } return -1; }

计算器#

Description#

你被要求设计一个计算器完成以下三项任务:

1、给定y,z,p,计算Y^Z Mod P 的值;

2、给定y,z,p,计算满足xy≡ Z ( mod P )的最小非负整数;

3、给定y,z,p,计算满足Y^x ≡ Z ( mod P)的最小非负整数。

Input Format#

输入包含多组数据。

第一行包含两个正整数T,K分别表示数据组数和询问类型(对于一个测试点内的所有数据,询问类型相同)。

以下行每行包含三个正整数y,z,p,描述一个询问。

Output Format#

对于每个询问,输出一行答案。对于询问类型2和3,如果不存在满足条件的,则输出“Orz, I cannot find x!”,注意逗号与“I”之间有一个空格。

Sample Input#

Copy
3 1 2 1 3 2 2 3 2 3 3

Sample Output#

Copy
2 1 2

解析#

同余方程模板题。对于询问1,快速幂解决,对于询问2,扩展欧几里得算法解决,对于询问3BSGS算法解决。

对于第三个任务,一个细节要注意的是,题中保证了p为质数,却没保证a不是p的倍数。所以要特判ap倍数的情况。

Code:

Copy
#include<bits/stdc++.h> using namespace std; #define mset(name,val) memset(name,val,sizeof name) long long t,k; inline void input(void) { scanf("%lld%lld",&t,&k); } inline long long power(long long a,long long b,long long p) { long long res=1; while (b) { if (1&b)res*=a,res%=p; b>>=1; a*=a,a%=p; } return res; } inline long long Exeuclid(long long a,long long &x,long long b,long long &y,long long c) { if (b==0){x=c/a,y=0;return a;} else { long long p=Exeuclid(b,x,a%b,y,c); long long x_=x,y_=y; x=y_;y=x_-a/b*y_; return p; } } inline long long Euclid(long long a,long long b) { return b ? Euclid(b,a%b) : a; } inline long long Baby_Step_Giant_Step(long long a,long long b,long long p) { map < long long , long long > hash;hash.clear(); b%=p;long long t=(long long)sqrt(p)+1,mul=1; for (int j=0;j<t;j++) { hash[ mul * b % p ] = j; mul = mul * a % p; } if (a%p==0)return b==0 ? 1 : -1; a = 1; for (int i=0;i<=t;i++) { long long j = ( hash.find(a) != hash.end() ? hash[a] : -1 ); if (j>=0&&i*t-j>=0)return i*t-j; a = a * mul % p; } return -1; } inline void solve(void) { long long y,z,p; for (int i=1;i<=t;i++) { scanf("%lld%lld%lld",&y,&z,&p); if (k==1) printf("%lld\n",power(y,z,p)); if (k==2) { if (z%Euclid(y,p)) { printf("Orz, I cannot find x!\n"); continue; } long long x_,y_; Exeuclid(y,x_,p,y_,z); printf("%lld\n",(x_%p+p)%p); } if (k==3) { long long ans=Baby_Step_Giant_Step(y,z,p); if (ans==-1) printf("Orz, I cannot find x!\n"); else printf("%lld\n",ans); } } } int main(void) { input(); solve(); return 0; }

ExBSGS算法#

顾名思义,该算法就是Baby Step Giant Step的拓展。对于方程axb(mod p),当a,p不互质的时候,我们可以使用该算法进行求解。

不难发现,当互质条件失去后,无解的情况也随之增加。经过严格的数学证明可以得到:gcd(a,p)/|bb1时,该方程无自然数解(转换成扩欧方程的有解性判定,由裴蜀定理可得)。

b=1的情况是需要特判的,x最小非负整数解为0。除了无解和该情况以外,我们可以用如下方法求解:

d=gcd(a,p),将原式转换为 :ax1 ad  bd (mod pd)

x1=x1,k1=ad,b1=bd,p1=pd,则

k1ax1b1(mod p1)

若能够求解该方程,则原方程的解x=x1+1

记录下k,则这个方程的结构是可以进行同样的操作的。如此,不断缩小模数p,直到gcd(a,p)=1,得到方程$$k_1k_2...k_na^{x_n}\equiv b_n(mod\ p_n)$$

其中,gcd(an,pn)=1

k=ni=1ki,则kaxnbn(mod pn)

直接使用BSGS算法求解该方程,则原方程的解x=xn+n

Code:

Copy
inline long long ExBSGS(long long a,long long b,long long p) { if (b==1)return 0; long long cnt=0,d,k=1; while ( ( d=Euclid(a,p) ) ^ 1 ) { if (b%d)return -1; b/=d,p/=d,++cnt; k = k * (a/d) % p; if (k==b)return cnt; } unordered_map < long long , long long > Hash;Hash.clear(); long long t=(long long)sqrt(p)+1,mul=1; for (int j=0;j<t;j++) { Hash[ mul * b % p ] = j; mul = mul * a % p; } for (int i=0;i<=t;i++) { long long j = ( Hash.find(k) != Hash.end() ? Hash[k] : -1 ); if (j>=0&&i*t-j+cnt>=0)return i*t-j+cnt; k = k * mul % p; } return -1; }

<后记>

posted @   Parsnip  阅读(756)  评论(0编辑  收藏  举报
编辑推荐:
· 软件产品开发中常见的10个问题及处理方法
· .NET 原生驾驭 AI 新基建实战系列:向量数据库的应用与畅想
· 从问题排查到源码分析:ActiveMQ消费端频繁日志刷屏的秘密
· 一次Java后端服务间歇性响应慢的问题排查记录
· dotnet 源代码生成器分析器入门
阅读排行:
· 互联网不景气了那就玩玩嵌入式吧,用纯.NET开发并制作一个智能桌面机器人(四):结合BotSharp
· Vite CVE-2025-30208 安全漏洞
· 《HelloGitHub》第 108 期
· MQ 如何保证数据一致性?
· 一个基于 .NET 开源免费的异地组网和内网穿透工具
点击右上角即可分享
微信分享提示
目录