专栏文章

大步小步/BSGS

算法·理论参与者 1已保存评论 0

文章操作

快速查看文章及其快照的属性,并进行相关操作。

当前评论
0 条
当前快照
1 份
快照标识符
@mioof5ub
此快照首次捕获于
2025/12/02 22:31
3 个月前
此快照最后确认于
2025/12/02 22:31
3 个月前
查看原文
BSGS 全称为 baby step giant step,用于求最小的 xx 满足 axb(modp)a^x\equiv b\pmod p
主要思路基于分块与哈希。
首先我们知道若 gcd(a,p)=1\gcd(a,p)=1,那么满足 apa(modp)a^p\equiv a \pmod p(费马小定理)。所以这意味着若有答案,则 xx 一定小于 pp
显然暴力枚举是不可行的,而 BSGS 算法基于分块使时间复杂度降低到了根号级别,可以通过。
具体而言,令 siz=nsiz=\lceil\sqrt n\rceil(防止未全部覆盖),则 xx 可以表示为 i×sizj,j[0,siz)i\times siz-j,j\in[0,siz)
化简式子:
ai×sizjb(modp)a^{i\times siz-j}\equiv b\pmod p ai×sizb×aj(modp)a^{i\times siz}\equiv b\times a^j\pmod p
因为 i,ji,j 的范围都在 [0,siz)[0,siz) 之间(ii 的范围是因为 i×sizpi\times siz\le p),所以我们考虑暴力预处理出每个 jj 对应的 b×ajmodpb\times a^j\bmod p,再把每个元素放到桶里,枚举每个 ii,找到对应的 jj,最终答案即为最先找到的 i×sizji\times siz-j(由于 ii 越小 i×sizji\times siz-j 越小)。时间复杂度为 Θ(n)\Theta(\sqrt n)
具体实现,hash 我选择 unordered_map,速度接近 Θ(1)\Theta(1)
CPP
int BSGS(int a,int b,int p) {
  if(b == 1) return 0; //几个特判
  if(a % p == b % p) return 1;
  if(a % p == 0) return -1;
  unordered_map<int,int> hash;
  int siz = ceil(sqrt(p));
  for(int i = 0,base = b % p;i <= siz;i++) hash[base] = i,base = base * a % p;  //预处理任意j对应的结果
  for(int i = 1,base = Pow(a,siz,p),tmp = 1;i <= siz;i++) {
  	tmp = tmp * base % p;
    if(hash[tmp]) return (i * siz - hash[tmp]) % p; //容易发现i*siz-hash[tmp]>=0,所以无需+p再%p
  }
  return -1;
}
拼好题,第一问用快速幂,第二问求 x×inv(y)modpx\times inv(y)\bmod p,第三问 BSGS 板子。

题意:求最小的 ii 满足 xit(modp)x_i\equiv t\pmod p
观察式子,是一个递推的形状,:
x2=ax1+bx_2=ax_1+b x3=ax2+b=a(ax1+b)+bx_3=ax_2+b=a(ax_1+b)+b x4=ax3+b=a(a(ax1+b)+b)+bx_4=ax_3+b=a(a(ax_1+b)+b)+b
容易发现,xi=ai1x1+bj=0i1ajx_i=a^{i-1}x_1+b\sum\limits_{j=0}^{i-1}a^j
接下来是等比公式的相关应用,学过的可以跳过。
S=j=0i2ajS=\sum\limits_{j=0}^{i-2} a^j
则:
aS=j=1i1ajaS=\sum\limits_{j=1}^{i-1}a^j
上式减下式:
SaS=1ai1S-aS=1-a^{i-1} S(1a)=1ai1S(1-a)=1-a^{i-1} S=1ai11aS=\frac{1-a^{i-1}}{1-a}
我们回到刚刚的式子:
xi=ai1x1+b×1ai11ax_i=a^{i-1}x_1+b\times\frac{1-a^{i-1}}{1-a}
tt 放进来:
tai1x1+b×1ai11a(modp)t\equiv a^{i-1}x_1+b\times\frac{1-a^{i-1}}{1-a}\pmod p tai1x1+b1aai1b1a(modp)t\equiv a^{i-1}x_1+\frac{b}{1-a}-a^{i-1}\frac{b}{1-a}\pmod p tai1(x1b1a)+b1a(modp)t\equiv a^{i-1}(x_1-\frac{b}{1-a})+\frac{b}{1-a}\pmod p (x1b1a)ai1tb1a(x_1-\frac{b}{1-a})a^{i-1}\equiv t-\frac{b}{1-a} ai1tb1ax1b1aa^{i-1}\equiv\frac{t-\frac{b}{1-a}}{x_1-\frac{b}{1-a}}
然后就转化为了 BSGS 的板子了。容易发现当 a=0a=0a=1a=1 的不能很好用这个式子解决,所以特判处理。部分细节看代码。
CPP
#include <bits/stdc++.h>
using namespace std;

template<class T>T Pow(T a,T b,T p){T ans=1;for(;b;b/=2){ans=((b&1?a:1)*ans%p),a=a*a%p;}return ans;}
template<class T>T Inv(T a,T b){return Pow(a,b-2,b);}

#define int long long 

const int _ = 1e5 + 5;

int BSGS(int a,int b,int p) {
  unordered_map<int,int> hash;
  int siz = ceil(sqrt(p));
  for(int i = 0,base = b % p;i <= siz;i++) hash[base] = i,base = base * a % p;
  for(int i = 1,base = Pow(a,siz,p),tmp = 1;i <= siz;i++) {
  	tmp = tmp * base % p;
    if(hash[tmp]) return ((i * siz - hash[tmp]) % p + p) % p;
  }
  return -1;
}

void solve() {
  int p,a,b,x,t;
  cin >> p >> a >> b >> x >> t;
  if(x == t) { //相等直接输出1
  	cout << 1 << '\n';
  } else if(a == 0) { //a=0时b=t
  	cout << (b == t ? 2 : -1) << '\n';
  } else if(a == 1) { //a=1时注意答案是p的话就不要再取模了
  	t = ((t - x) % p + p) % p;
  	if(t % __gcd(b,p)) cout << -1 << '\n';
  	else {
  	  if((t * Inv(b,p) + 1) % p == 0) cout << p << '\n';
  	  else cout << (t * Inv(b,p) + 1) % p << '\n';
    }
  } else {
    int inv = Inv(1 - a,p);
    int ans = BSGS(a,((t - inv) % p + p) % p * Inv(((x - inv) % p + p) % p,p),p) % p;
	if(ans == -1) cout << -1 << '\n';
	else cout << ans + 1 << '\n';
  }
}

signed main() {
  int tt;
  cin >> tt;
  while(tt--) solve();
  return 0;
}

11111\dots1 这样的数不好转化,若我们能将其表示为某个数的次幂就好了。想到了 999=10i199\dots9=10^i-1,则 111=10i1911\dots1=\frac{10^i-1}{9}
同余式子转化为:10i19k(modp)\frac{10^i-1}{9}\equiv k\pmod p,化简得 10i9k+1(modp)10^i\equiv9k+1\pmod p,成功转化为了 BSGS 的样子。
好像要用 __int128 或者龟速乘,我选择 __int128。

评论

0 条评论,欢迎与作者交流。

正在加载评论...