学习笔记:BSGS(拔山盖世?)算法
其实这个东西挺简单的,就是紫题了。
先放板子题:P2485 [SDOI2011]计算器
操作一二就不说了,反正这里是讲的是 \(BSGS\)。
\(BSGS\) 是解决离散对数问题的,全称 \(Baby\;Step\;Gaint\;Step\) (不是拔山盖世),即求:
\[a^x\equiv b\pmod{p}
\]
的最小整数解 \(x\)。
但要保证 \(p\in P\)(质数集合)。
那么我们可以发现\(x\)变化时得到的余数会出现循环节,长度大概是 \(\left\lceil\sqrt{n}\right\rceil\) 的。
那我们令 \(m= \left\lceil\sqrt{n}\right\rceil\),
就会存在常数 \(u,v\) 使:
\[a^{um+v}\equiv b\pmod{p}
\]
即:
\[a^{um}\equiv ba^{-v}\pmod{p}
\]
这样需要处理逆元,可有没有好点的方法呢?
不妨设 \(x=um-v\),
我们可以知道:
\[a^{um}\equiv ba^{v}\pmod{p}
\]
我们不妨枚举同余号任意一侧(下面代码是右边),存一下(这时要去重),然后枚举另一边,寻找一一对应关系即可。
显然这时枚举两边的上界都是 \(\sqrt{p}\) 的,这时是最小的。
注意这里枚举的边界:\(v\) 是 \([0,m),u\)是\((0,m]\)(由于我们其实是把\(u\)在本来正常想法上 \(+1\))。
如果有枚举一边得到的两对数有相同的对应值,那贪心的选用编号大的,另一边选编号小的,再二分找一下即可。
然后要注意如果余数是 \(1\),直接得 \(0\) 就好了,不知道为啥算不出来。
综上,复杂度是 \(\mathcal O(\sqrt{p}\log\sqrt{p})=\mathcal O(\sqrt{p}\log p)\),可以通过本题,是要比传统的\(map\)跑得快的。
(上面变量名很清奇......不要吐槽就好)
下面就是代码了:
\(Code\):
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
int t,flag;
ll a,b,p;
ll quickpow(ll a,ll b)
{
ll ans=1,base=a;
while(b)
{
if(b&1) ans=ans*base%p;
b>>=1;
base=base*base%p;
}
return ans%p;
}
ll x,y;
void exgcd(ll a,ll b)
{
if(b==0)
{
x=1,y=0;
return;
}
exgcd(b,a%b);
ll t=x;
x=y;
y=t-a/b*y;
}
ll gcd(ll a,ll b){return b==0?a:gcd(b,a%b);}
ll sol(ll a,ll b,ll c)
{
ll g=gcd(a,b);
if(c%g) return -1;
a/=g,b/=g,c/=g;
exgcd(a,b);
x*=c;
while(x<0) x+=b;
x%=b;
return x;
}
struct node
{
int id,val;
}book[320005],dic[320005];
int c=0,cnt=0,h;
bool cmp(node n,node m){if(n.val^m.val) return n.val<m.val;else return n.id<m.id;}
void get(int h,ll a,ll b,ll p)
{
ll now=1;
for(int i=0;i<h;i++)
{
book[++c].val=now*b%p;
now=now*a%p;
book[c].id=i;
}
return;
}
void process()
{
sort(book+1,book+c+1,cmp);
for(int i=1;i<=c;i++)
{
if(book[i].val!=dic[cnt].val) dic[++cnt]=book[i];
else dic[cnt]=book[i];
}
return;
}
ll find(int h,ll a)
{
ll d=quickpow(a,(ll)h),now=1;
for(int i=1;i<=h+1;i++)
{
now=now*d%p;
int l=1,r=cnt;
while(l<r)
{
int mid=(l+r)>>1;
if(now>dic[mid].val) l=mid+1;
else r=mid;
}
if(dic[l].val!=now) continue;
else return ll(i*h-dic[l].id);
}
return -1;
}
int main()
{
scanf("%d%d",&t,&flag);
while(flag==1)
{
if(!t) break;
t--;
scanf("%lld%lld%lld",&a,&b,&p);
printf("%lld\n",quickpow(a,b));
}
while(flag==2)
{
if(!t) break;
t--;
scanf("%lld%lld%lld",&a,&b,&p);
b%=p;
ll cur=sol(a,p,b);
if(cur==-1) printf("Orz, I cannot find x!\n");
else printf("%lld\n",cur);
}
while(flag==3)
{
if(!t) break;
t--;
scanf("%lld%lld%lld",&a,&b,&p);
b%=p;
if(b==1)
{
printf("0\n");
continue;
}
h=ceil(sqrt((int)p));
c=0,cnt=0;
memset(book,0,sizeof(book));
memset(dic,0,sizeof(dic));
get(h,a,b,p);
process();
ll cur=find(h,a);
if(cur<0) printf("Orz, I cannot find x!\n");
else printf("%lld\n",cur);
}
return 0;
}
当然\(p\)非质数也能解,以后等我学了再说吧。

浙公网安备 33010602011771号