zoukankan      html  css  js  c++  java
  • [模板] [另解] 扩展BSGS

    [题解] [模板] [另解] 扩展BSGS

    零.前言

    ​ 今天听了一点课,然后开始整活,目前没有在题解里面看到这种做法,所以来发一篇。此篇涉及到一点点的群论定义,但是只是皮皮皮皮毛,很好理解,提供理论支撑,与代码无关。

    ​ 理论上来讲,看这篇题解,从学会 BSGS 到它的扩展版,只需要 5分钟(若你对群没有丝毫了解,则需 10 分钟),并且也有不用群论的数论解释,前提你要知道扩展欧拉定理。

    一.半群

    ​ 觉得看不懂的可以不看,看之后的数论解释,而且多半也没什么用

    ​ 半群是最简单、最自然的一类代数系统。一个非空集合S连同定义在它上面的一个结合的(即满足结合律的)二元运算 (cdot) 的代数系统 $(S,cdot) $ 称为一个半群。(|S|) 称为这个半群的大小

    ​ 很显然我们定义一个集合 S 为(S={x|xequiv a^y pmod p}),而定义的乘法也满足结合律,为二元运算,这样组成了一个半群,而我们的算法的理论基础就建立在这个上面。

    ​ 理论上任意的半群都可以拿来跑 BSGS ,而且时间复杂度均为 (O(sqrt{|S|}))

    二.数论解释

    ​ 首先将 "乘以 a " 看为一条出边,连接向结果,基于扩展欧拉定理,会有循环节。更具体的说,将起点设为 1 ,那么会形成一个形如 ( ho) 的图,即在某一刻进入循环节,然后再环上不断跑。

    ​ 为了方便,我们称循环节的部分为 环,非循环节的部分为 柄。

    ​ 容易知道这个图的大小(点数)不会超过 (2*varphi(p)) ,这个是依据扩展欧拉定理来的,柄和环分别都是上界为 (varphi(p)) .所以理论复杂度是 (O(sqrt{varphi(p)})) ,好耶!并且环的大小必然为(varphi(p)) 的因子。

    ​ 当然实际会有哈希表和乘法定义的复杂度,这里就八仙过海了。

    三.具体操作

    ​ 还是像普通BSGS,将大步的大小 (M) 算为 (sqrt{(2*varphi(p))}) 预处理出大步和小步(懂得都懂吧,小步就是 (b*a^x) ,大步就是 (a^M,a^{2M}dots)) ,只是不同于不同 BSGS 的有两点。

    • 哈希表里面存的大步而非小步,这样具有十分优美的形态。
    • 每一个地方都需要存两个最小的对应指标,理由待会再说。

    然后直接大力跑就行了。若是不一定逆元,最后需要检验答案,即 (a^{kM-q}equiv b) 这个式子是否成立。检验一定要检验两个,即拿最小得两个可能是答案得 (k) 拿去检验。检验是因为逆元不一定存在,至于两个的原因,现在来说。

    ​ 考虑到 (kM-q) 的意义相当于在图上倒退 q 步。那么如果是在环上第一圈,则可能倒退回柄上,若非第一圈则倒退至环上,也就是可能有两个地方。而最小和次小之间最小差一整圈,所以不会出现在两个都在柄上的情况。

    ​ 下面给出代码。

    ​ 由于我们所有可能的都考虑了,是不会漏解的,只会有增根,所以要检验,非要类比就好像是解方程时给根式平方了,然后有一个负数的增根。

    CODE

    #define int long long
    #define fe(i,a,b) for(int i=a;i<=b;++i)
    inline int read(){
    	...
    }
    inline int get_phi(int x){
    	int res=x;
    	for(int i=2;i*i<=x;++i){
    		if(x%i==0){
    			res=res/i*(i-1);
    			while(x%i==0)x/=i;
    		}
    	}
    	if(x!=1)res=res/x*(x-1);
    	return res;
    }
    #define pii pair<int,int>
    #define mp(a,b) make_pair(a,b)
    inline int KSM(int a,int b,int mod){
    	int res=1;
    	for(;b;b>>=1,a=(a*a)%mod)if(b&1)res=(res*a)%mod;
    	return res;
    }
    unordered_map<int,pii> ha;
    int ans,Baby[100000],a,b,p,Gaint[100000];
    inline bool check(int c,int siz,int i){
    	return (b%p)==(Gaint[c-1]*Baby[siz-i]%p);
    }
    signed main(){
    	while(1){
    		a=read(),p=read(),b=read();
    		if(!a&&!b&&!p)break;
    		int phi=get_phi(p);
    		int siz=ceil(sqrt(2*phi)),alf=KSM(a,siz,p),temp=1;
    		ha.clear(),ans=-1;
    		fe(i,0,siz){
    			Gaint[i]=temp;
    			pii u=ha[temp];
    			if(u.first==0)u.first=i;
    			else if(u.first>i)u.second=u.first,u.first=i;
    			else if(u.second>i||u.second==0)u.second=i;
    			ha[temp]=u;
    			temp=(temp*alf)%p;
    		}
    		Baby[0]=1;
    		fe(i,1,siz)Baby[i]=(Baby[i-1]*a)%p;
    		fe(i,0,siz-1){
    			pii u=ha[(b*Baby[i])%p];
    			if(u.first!=0&&check(u.first,siz,i))ans=(ans==-1||ans>u.first*siz-i)?u.first*siz-i:ans;
    			else if(u.second!=0&&check(u.second,siz,i))ans=(ans==-1||ans>u.second*siz-i)?u.second*siz-i:ans;
    		}
    		if(ans==-1)puts("No Solution");
    		else printf("%lld
    ",ans);
    	}
    	return 0;
    }
    

    ​ 考场实现当然可以手写一个 hash 表,每个位置也只用挂两个,(机房卷老写了两个哈希表,说好写一些)这里偷懒写了map。

    ​ 然后存小步的写法我还没怎么思考,感觉不如存大步优美,好像还要逆元什么的,多半不好写。而且存大步可以解决对于同一个模数与底数的多次询问,会舒服一些。

  • 相关阅读:
    C++——overloading
    C++——调用优化
    C++——Big Three(copy ctor、copy op=、dtor)
    C++——引用 reference
    C++——构造函数 constructor
    003——矩阵的掩膜操作
    002——加载、修改、保存图像
    001——搭建OpenCV实验环境
    宏——基础
    剖析可执行文件ELF组成
  • 原文地址:https://www.cnblogs.com/clockwhite/p/14432701.html
Copyright © 2011-2022 走看看