大步小步算法:婴儿一小步,巨人一大步。

11490DX Re: Master Lv.15

 

BSGS 算法

给定一个质数 ,以及一个整数 ,一个整数

现在要求你计算一个最小的非负整数 ,满足 。保证

因为 ,并且 是质数,我们很容易就得到了 是互质的。

根据扩展欧拉定理:当 时,。于是我们可以得出,最小的非负整数 一定在 之间。可以看作在 之间一定可以找出答案。

这个算法就是利用了把 拆开,在 的时间复杂度内计算出答案。具体地说就是:

我们把 拆为 ,这里令

那么变换一下原式:

于是可以发现 的值域最多只有 个,可以首先把这 个值域先插入到哈希表内。

具体地、从 枚举 ,然后计算出每一个 对应的 ,然后把它当作哈希表的 ,其对应的 就是 。如果后面有覆盖,那么取更大的那个。因为 更大,相应的 就可以更小了。这一步操作就相当于是婴儿一小步。

然后从 枚举 ,计算这个时候的 。如果有在哈希表内存在 ,那么我们就得出来答案了。最小的 就是 这一步操作就可以看作巨人一大步。

如果遍历完了 还是没有找到那个 ,那么无解。

由于这种方法算不出来 的情况,所以当 的时候,就特判,然后直接输出

所以这种方法非常之妙,把原本 的复杂度直接砍到

只有 676 Bytes 的简短 Code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include<bits/stdc++.h>
using namespace std;
int p, b, n;
typedef long long ll;
unordered_map<int, int> hashtb;
int main(){
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int p, a, b;
cin>>p>>a>>b;
if(b == 1) return cout<<'0', 0;
int m = ceil(sqrt(p));
ll bas = b;
ll stt = 1;
for(int i=0;i<m;i++){
hashtb[bas] = i;
bas = bas * a % p;
stt = stt * a % p;
}
ll now = 1;
for(int i=1;i<=m;i++){
now = now * stt % p;
if(hashtb.count(now)){
cout<<i*m-hashtb[now]<<'\n';
return 0;
}
}
cout<<"no solution\n";
return 0;
}

所以其实,普通的 BSGS 算法只要求 互质即可。不要求 p 为质数

例题

P4884 多少个 1?

给定整数 和质数 ,求最小的正整数 ,使得

说人话:就是

的数据保证 ,保证 是质数。

从这个鬼玩意想到 BSGS 还是比较有难度的。

具体来说,就是 组成的数 ,把它乘上 就变成了 组成的数,再加上 就变成了

于是原式就可以转化为:

然后移项:

然后就变成了板子。就可以做了。但是因为 ,所以我的代码中使用了龟速乘。


Ex-BSGS 算法

给定一个整数 ,以及一个整数 ,一个整数

现在要求你计算一个最小的非负整数 ,满足 。保证

和之前的题目有什么不同?这里的 不再和 互质了。也就是说,我们不能再直接用 BSGS 算法来计算出答案了。但是我们可以对 BSGS 算法稍微加以改造,就可以过这道题。

首先还是原式 ,如果 ,那么直接搞 BSGS。否则按照以下步骤进行:

原式它可以等价于 。这个方程可以看成 exgcd 状物,有解的条件显然就是 。如果 ,那么原方程就无解。

如果有解的话,设 ,可以把这个方程化为 。然后就等价于 。就变为了一个新的类似 的式子。

然后继续检查 是否为 ,如果不是,那么就继续按照上面的步骤进行。

最终,等到最大公约数为 之后,令 ,就得到了一个形如 的式子。这里的 就是转化 的次数。

。原式就变为了

由于此时的 是互质的,,所以 也和 互质。所以 就有了模 意义下的逆元(但是千万不要用快速幂求!因为 P 不一定是质数!!!)。

然后求出 的指数了之后,加上 就是我们要求的 了。

核心代码 - Ex-BSGS 和改装后的 BSGS:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
void bsgs(ll a, ll b, ll mod, ll k, ll A){
a %= mod, b %= mod;
unordered_map<int, int>mp;
ll m = ceil(sqrt(mod));
ll bas=1, now=b;
for(int i=0;i<m;i++){
mp[now] = i;
now=now*a%mod;
bas=bas*a%mod;
}
now = A;
for(int i=1;i<=m;i++){
now = now*bas%mod;
if(mp.count(now)){
cout<<i*m-mp[now]+k<<'\n';
return ;
}
}
cout<<"No Solution\n";
}
void exbsgs(ll a, ll b, ll mod){
a %= mod, b %= mod;
if(b==1||mod==1) return cout<<"0\n", void();
ll g = gcd(a, mod);
ll k = 0;
ll A = 1;
while(g^1){
if(b%g) return cout<<"No Solution\n", void();
k ++;
b /= g, mod /= g;
A = (A*(a/g)) % mod;
g = gcd(a, mod);
if(A == b) return cout<<k<<'\n', void();
}
bsgs(a, b, mod, k, A);
}

Link:BSGS 算法练习题单

  • Title: 大步小步算法:婴儿一小步,巨人一大步。
  • Author: 11490DX
  • Created at : 2025-01-08 21:19:03
  • Updated at : 2025-02-14 13:08:42
  • Link: https://11490dx.net/2025/01/08/math-bsgs/
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments
On this page
大步小步算法:婴儿一小步,巨人一大步。