\(Baby \ Step \ Giant \ Step\)
这个算法分为:狭义\(Bsgs\rightarrow exBsgs \rightarrow\) 广义 \(Bsgs\)
狭义BSGS
引入
求:满足一个最小的\(x\),满足:
其中 \(A,B\) 给定,\(p\in \{\varphi(x)=x-1\}\)
过程
首先一个结论:我们取出\(A^0\rightarrow A^{p-1}\) 必然有一个 \(A^q\equiv B(mod\ p)\)
如果你不知道抽屉原理……(其实就是个同余系下的一通操作)
但是我们发现并不能这样,因为时间不允许
然后我们这样操作一发:
任意给个 \(S\) 求出来:$B \times A^1\rightarrow B \times A^{S-1} \ mod \ p $ 的值,放到一个 \(hash\) 表中
然后我们再求:\(A^{0\times S} \rightarrow A^{\lfloor\frac{p}{S}\rfloor\times S}\)
对于每个 \(A^S\) 的次幂,我们移项,转化成下式:
然后我们把它扔到我们原来的 \(hash\) 表里面找
如果找到了,答案就是 \(k \times 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\n",(t%p+p)%p); fl=1; break;
}
拓展 \(BSGS\)
引入
如果 \(p\) 不是质数了呢?
过程
可能在乘法的过程中把质因子凑全了
先考虑 \(B=0\) 的情况
换一下式子发现:
其中 \(k\) 为整数
那我们枚举 \(x\) 同时在每一步除掉 \(gcd(A^x,p)\)
如果什么时候 \(p=1\), \(x\) 为最小解
这里如果 \(gcd(A,p)=1\) 那么无解
然后是正常情况:
然后推一发有:
\(k\) 是任意的一个整数
由裴蜀定理:
如果 \(B\%gcd(A,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\n",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\n",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\n",i*s+res-mp[now]),void();
} puts("No Solution");
return ;
}
广义BSGS
形式:\(f(f(f(f(x))))=k(mod\ p)\)
狭义的普通 \(bsgs\) 对应就是 \(f(x)=x\times A\)
如果我们把 \(f(x)\) 看成矩阵什么的也一样能做
我们发现其实在 \(bsgs\) 中存在一部求逆函数的操作,其实就是逆元
然后我们对于要求的\(f(x)\),找到相应的逆函数即可
逆函数可以是逆矩阵,向量的逆啥的
应用是求广义斐波那契数列的循环节
例题:\(bzoj4128\)
矩阵求逆之后上广义bsgs即可,板子题
然而我们可以直接偷懒换式子:
所以我们把右边的所有结果存一存,左边就上矩阵乘就好了,并不用求逆
对于判定矩阵相等,我们 \(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\n",i*s-mp[tmp]);
}
return 0;
}
}
signed main(){return yspm::main();}