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

  • 相关阅读:
    人脸开锁
    xtrabackup 8.0如何恢复单表
    这样配置,让你的VS Code好用到飞起!
    sqlserver安装教程(sql server2019安装详细步骤)
    史上最全vscode配置使用教程
    CxSAST 安装教程
    AppScan下载、安装、使用教程
    查看mysql数据库表结构注释信息
    从外包增删改查到大厂技术专家,我做对了什么?
    C内存操作API的实现原理
  • 原文地址:https://www.cnblogs.com/tlx-blog/p/12422626.html
Copyright © 2020-2023  润新知