学习笔记:BSGS(拔山盖世?)算法

其实这个东西挺简单的,就是紫题了。
先放板子题:P2485 [SDOI2011]计算器
操作一二就不说了,反正这里是讲的是(BSGS)
(BSGS)是解决离散对数问题的,全称(Baby;Step;Gaint;Step)(不是拔山盖世),即求:

[a^xequiv bpmod{p} ]

的最小整数解(x)
但要保证(pin P)(质数集合)。
那么我们可以发现(x)变化时得到的余数会出现循环节,长度大概是 (leftlceilsqrt{n} ight ceil) 的。
那我们令(m= leftlceilsqrt{n} ight ceil)
就会存在常数(u,v)使:

[a^{um+v}equiv bpmod{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)就好了,不知道为啥算不出来。
综上,复杂度是(O(sqrt{p}logsqrt{p})=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
",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!
");
		else printf("%lld
",cur);
	}
	while(flag==3)
	{
		if(!t) break;
		t--;
		scanf("%lld%lld%lld",&a,&b,&p);
		b%=p;
		if(b==1)
		{
			printf("0
");
			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!
");
		else printf("%lld
",cur);
	}
	return 0;
}

当然(p)非质数也能解,以后等我学了再说吧。

原文地址:https://www.cnblogs.com/tlx-blog/p/12422626.html