zoukankan      html  css  js  c++  java
  • 【学习笔记】Baby Step Giant Step 算法

    (Baby Step Giant Step)

    这个算法分为:狭义(Bsgs ightarrow exBsgs ightarrow) 广义 (Bsgs)

    狭义BSGS

    引入

    求:满足一个最小的(x),满足:

    [A^xequiv B(mod p) ]

    其中 (A,B) 给定,(pin {varphi(x)=x-1})

    过程

    首先一个结论:我们取出(A^0 ightarrow A^{p-1}) 必然有一个 (A^qequiv B(mod p))

    如果你不知道抽屉原理……(其实就是个同余系下的一通操作)

    但是我们发现并不能这样,因为时间不允许

    然后我们这样操作一发:

    任意给个 (S) 求出来:$B imes A^1 ightarrow B imes A^{S-1} mod p $ 的值,放到一个 (hash) 表中

    然后我们再求:(A^{0 imes S} ightarrow A^{lfloorfrac{p}{S} floor imes S})

    对于每个 (A^S) 的次幂,我们移项,转化成下式:

    [A^{k imes S}equiv {B imes {A^ p}} ]

    然后我们把它扔到我们原来的 (hash) 表里面找

    如果找到了,答案就是 (k imes S-p)

    复杂度分析:

    默认我们的 (hash) 是个 (O(1))

    那么复杂度是个(O(S+frac {mod} S ))

    这个是个对勾函数对吧,显而易见的在 (S= sqrt P) 时我们可以得到该函数的最小值

    Code

    考虑到我不会写正常的(hash) ,我选择了 (map)

    	mp.clear(); int s=ceil(sqrt(p)),t=z%p; mp[t]=0;
    	for(int i=1;i<=s;++i)
    	{
    		t=t*y%p; mp[t]=i;
    	}
    	int base=ksm(y,s,p),now=1; bool fl=0;
    	for(int i=1;i<=s;i++)
    	{
    		now*=base; now%=p;
    		if(!mp.count(now)) continue;
    		int t=i*s-mp[now]; 
    		printf("%lld
    ",(t%p+p)%p); fl=1; break;
    	} 
    

    拓展 (BSGS)

    引入

    如果 (p) 不是质数了呢?

    过程

    可能在乘法的过程中把质因子凑全了

    先考虑 (B=0) 的情况

    换一下式子发现:

    [k=frac {A^{x}} p ]

    其中 (k) 为整数

    那我们枚举 (x) 同时在每一步除掉 (gcd(A^x,p))

    如果什么时候 (p=1)(x) 为最小解

    这里如果 (gcd(A,p)=1) 那么无解

    然后是正常情况:

    [A^xequiv B (mod p) ]

    然后推一发有:

    [A^{x-1} imes A equiv B (mod p) ]

    [A^{x-1} imes A-kp=b ]

    (k) 是任意的一个整数

    由裴蜀定理:

    如果 (B\%gcd(A,p)) 无解

    然后我们把等式两边除一下 (gcd(A,p))

    则有:

    [frac{A}{gcd(A,p)} imes A^x equiv frac B {gcd(B,p)} (mod frac{p}{gcd(A,p)}) ]

    这个就是可以拿普通的 (BSGS) 做了对吧

    然后就没了

    板子题在 (Luogu) 上有

    Code

    inline void work()
    {
    	y%=p; z%=p;
    	if(z==1) return puts("0"),void();
    	if(!y&&!z) return puts("1"),void();
    	if(!y) return puts("No Solution"),void();
    	if(!z)
    	{
    		int res=0,d;
    		while((d=__gcd(y,p))!=1)
    		{
    			++res; p/=d;
    			if(p==1) return printf("%lld
    ",res),void();
    		}return puts("No Solution"),void();
    	}
    	int c=1,res=0,d;
    	while((d=__gcd(y,p))!=1)
    	{
    		if(z%d) return puts("No Solution"),void();
    		p/=d; z/=d; res++; (c*=y/d)%=p;
    		if(c==z) return printf("%lld
    ",res),void();
    	}
    	mp.clear(); int t=z%p,s=ceil(sqrt(p)); mp[t]=0;
    	for(int i=1;i<=s;++i) (t*=y)%=p,mp[t]=i;
    	int base=ksm(y,s,p),now=c;
    	for(int i=1;i<=s;++i)
    	{
    		(now*=base)%=p;
    		if(!mp.count(now)) continue;
    		return printf("%lld
    ",i*s+res-mp[now]),void();
    	} puts("No Solution"); 
    	return ;
    }
    

    广义BSGS

    形式:(f(f(f(f(x))))=k(mod p))

    狭义的普通 (bsgs) 对应就是 (f(x)=x imes A)

    如果我们把 (f(x)) 看成矩阵什么的也一样能做

    我们发现其实在 (bsgs) 中存在一部求逆函数的操作,其实就是逆元

    然后我们对于要求的(f(x)),找到相应的逆函数即可

    逆函数可以是逆矩阵,向量的逆啥的

    应用是求广义斐波那契数列的循环节

    例题:(bzoj4128)

    矩阵求逆之后上广义bsgs即可,板子题

    然而我们可以直接偷懒换式子:

    [A^{kS}=B imes A^t(mod p) ]

    所以我们把右边的所有结果存一存,左边就上矩阵乘就好了,并不用求逆

    对于判定矩阵相等,我们 (hash)

    Code

    这份代码被卡了常,但是我不想改了

    #include<bits/stdc++.h>
    using namespace std;
    #define int long long
    namespace yspm{
    	inline int read()
    	{
    		int res=0,f=1; char k;
    		while(!isdigit(k=getchar())) if(k=='-') f=-1;
    		while(isdigit(k)) res=res*10+k-'0',k=getchar();
    		return  res*f;
    	}
    	#define ull unsigned long long
    	const int N=80,b1=998244353,b2=1e9+7;
    	int n,p;
    	struct mat{
    		int a[N][N];
    		ull v1,v2;
    		inline void init()
    		{
    			for(int i=1;i<=n;++i) for(int j=1;j<=n;++j) a[i][j]=read();
    			return ;
    		}
    		mat operator * (const mat x) const
    		{
    			mat ans; memset(ans.a,0,sizeof(ans.a));
    			for(int i=1;i<=n;++i)
    			{
    				for(int j=1;j<=n;++j) 
    				{
    					for(int k=1;k<=n;++k)
    					{
    						ans.a[i][j]+=a[i][k]*x.a[k][j]%p; ans.a[i][j]%=p;
    					}
    				}
    			}return ans;
    		}
    		inline void hash()
    		{
    			for(int i=1;i<=n;++i) for(int j=1;j<=n;++j) v1=v1*b1+a[i][j],v2=v2*b2+a[i][j];
    			return ;
    		}
    	}t,now,y,z;
    	map<pair<ull,ull>,int> mp;
    	signed main()
    	{
    		n=read(),p=read();
    		y.init(); z.init();
    		for(int i=1;i<=n;++i) now.a[i][i]=t.a[i][i]=1;
    		int s=ceil(sqrt(p)); 
    		for(int i=1;i<=s;++i) t=t*y,z=z*y,z.hash(),mp[make_pair(z.v1,z.v2)]=i;
    		for(int i=1;i<=s;++i)
    		{
    			now=now*t; now.hash(); pair<ull,ull> tmp;
    			tmp=make_pair(now.v1,now.v2);
    			if(!mp.count(tmp)) continue;
    			printf("%lld
    ",i*s-mp[tmp]); 
    		} 
    		return 0;
    	}
    }
    signed main(){return yspm::main();}
    
  • 相关阅读:
    libevent中的bufferevent原理
    libevent中的事件机制
    libevent中数据缓冲区buffer分析
    libevent中最小堆实现算法解析
    我眼中的WebViewJavascriptBridge(图解)
    Tinyhttpd精读解析
    app微信支付的集成步骤
    java工厂模式的测试
    java Annotation 注解的使用
    android 连接蓝牙打印机 BluetoothAdapter
  • 原文地址:https://www.cnblogs.com/yspm/p/12819376.html
Copyright © 2011-2022 走看看