……

学习笔记: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\)非质数也能解,以后等我学了再说吧。

posted @ 2020-03-05 20:52  童话镇里的星河  阅读(360)  评论(0)    收藏  举报