『高次同余方程 Baby Step Giant Step算法』
<更新提示>
<第一次更新>
<正文>
高次同余方程#
一般来说,高次同余方程分ax≡b(mod p)和xa≡b(mod p)两种,其中后者的难度较大,本片博客仅将介绍第一类方程的解决方法。
给定a,b,p,其中gcd(a,p)=1,求方程ax≡b(mod p)的最小非负整数解。
普通分析和朴素算法#
先介绍一下欧拉定理:
如果正整数a,p互质,则aϕ(p)≡1(mod p)。
注意到题中所给的条件符合欧拉定理的前提条件,所以我们得知aϕ(p)≡1(mod p),又因为ϕ(p)≤p−1,a0≡1(mod p),所以在0到p−1范围内,模p意义下a的幂一定形成了至少一次循环。也就是说,我们只要在0到p−2范围内枚举解x,就一定能找到解,用快速幂判断即可求解该方程,时间复杂度O(plog2p)。
Baby Step Giant Step算法#
对于求解该类高次同余方程,Baby Step Giant Step可以优化枚举算法,在O(√p)的时间内求解该方程。
我们设解x=i∗t−j,其中0≤j≤t−1,则方程为ai∗t+j≡b(mod p)⇔(at)i≡b∗aj(mod q)。
对于所有的j∈[0,t−1],将b∗aj mod p的值插入hash表。
枚举i的所有可能取值[0,pt],计算出(at)i mod p的值,并在hash表中检索该值,如果该值存在,就可以用i∗t−j更新答案。
该算法可以保证枚举到x在[0,p−2]范围内的每一种情况,其时间复杂度为O(pt+p),该函数为经典的对勾函数,当参数t取√p时,其函数值最小,保证其时间复杂度为O(√p)。
以下给出求解方程最小非负整数解的程序,无解时返回−1。
Code:
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#
3 1
2 1 3
2 2 3
2 3 3
Sample Output#
2
1
2
解析#
同余方程模板题。对于询问1,快速幂解决,对于询问2,扩展欧几里得算法解决,对于询问3,BSGS算法解决。
对于第三个任务,一个细节要注意的是,题中保证了p为质数,却没保证a不是p的倍数。所以要特判a为p倍数的情况。
Code:
#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的拓展。对于方程ax≡b(mod p),当a,p不互质的时候,我们可以使用该算法进行求解。
不难发现,当互质条件失去后,无解的情况也随之增加。经过严格的数学证明可以得到:当gcd(a,p)/|b且b≠1时,该方程无自然数解(转换成扩欧方程的有解性判定,由裴蜀定理可得)。
b=1的情况是需要特判的,x最小非负整数解为0。除了无解和该情况以外,我们可以用如下方法求解:
令d=gcd(a,p),将原式转换为 :ax−1 ad ≡ bd (mod pd)
设x1=x−1,k1=ad,b1=bd,p1=pd,则
若能够求解该方程,则原方程的解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,则k∗axn≡bn(mod pn)
直接使用BSGS算法求解该方程,则原方程的解x=xn+n。
Code:
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;
}
<后记>
【推荐】还在用 ECharts 开发大屏?试试这款永久免费的开源 BI 工具!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 软件产品开发中常见的10个问题及处理方法
· .NET 原生驾驭 AI 新基建实战系列:向量数据库的应用与畅想
· 从问题排查到源码分析:ActiveMQ消费端频繁日志刷屏的秘密
· 一次Java后端服务间歇性响应慢的问题排查记录
· dotnet 源代码生成器分析器入门
· 互联网不景气了那就玩玩嵌入式吧,用纯.NET开发并制作一个智能桌面机器人(四):结合BotSharp
· Vite CVE-2025-30208 安全漏洞
· 《HelloGitHub》第 108 期
· MQ 如何保证数据一致性?
· 一个基于 .NET 开源免费的异地组网和内网穿透工具