(Baby Step Giant Step)
这个算法分为:狭义(Bsgs ightarrow exBsgs ightarrow) 广义 (Bsgs)
狭义BSGS
引入
求:满足一个最小的(x),满足:
其中 (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) 的次幂,我们移项,转化成下式:
然后我们把它扔到我们原来的 (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) 为整数
那我们枚举 (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
",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即可,板子题
然而我们可以直接偷懒换式子:
所以我们把右边的所有结果存一存,左边就上矩阵乘就好了,并不用求逆
对于判定矩阵相等,我们 (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();}