数论相关

1.逆元

引入

我们知道在同余系里面加,减,乘都是可以直接完成。
但是不能除。
我们要想在模 \(p\) 意义下除以一个数,就可以乘这个数的逆元。
这个数记作 \(x^{-1}\).
满足 \(x \cdot x^{-1} \equiv 1 \pmod p\)

线性求 \(1\sim n\) 逆元:
$i^{-1} = - (p \bmod i)^{-1}\cdot \left \lfloor \frac{p}{i} \right \rfloor $

证明:设 \(t=\left \lfloor \frac{p}{i} \right \rfloor\),\(r=p \bmod i\).

\(t*i+r\equiv 0 \pmod p\).
同时乘 \(i^{-1}\)
\(t+r*i^{-1} \equiv 0 \pmod p\).

\(i^{-1}\equiv -t * r^{-1} \pmod p\)

2.裴蜀定理/扩展欧几里得

引入

裴蜀定理

\(d=\gcd(x,y)\),则必有 \(ax+by=d\).
推论: \(a\perp b\) 的充要条件是 \(ax+by=d\).

扩展欧几里得算法

用于求解 \(ax+by=d\) 问题。

code
void exgcd(LL a,LL b,LL &x,LL &y) {
	if(b==0) {x=1; y=0; return ;}
	exgcd(b,a%b,x,y);
	int z=x; x=y; y=z-(a/b)*y;
}

这求出的是一组特解,设它们是 \(x_0,y_0\).
通解为:
\(x=x_0+k\cdot \frac{y}{d}\).
\(y=y_0+k\cdot \frac{x}{d}\).
$k \in \mathbb{Z} $.

对于任意形式的不定方程 \(ax+by=c\),只要把 \(x,y\) 同乘 \(\frac{c}{d}\) 即可。
\(d \not \mid c\),则无解。

扩欧还可以求解逆元。
只要求 \(x^{-1}*x+k*p=1\) 即可。
其中 \(x,p\) 为已知项,需求 \(x^{-1},k\).

应用

P3986 斐波那契数列

\(f\) 为原本斐波那契数列, \(g\) 为题目的数列。
考虑矩阵乘法:
我们得到一个重要性质:
\(g_i = a\cdot f_{i-2} + b\cdot f_{i-1}\)
于是我们只要解 \(k=a\cdot f_{i-2} + b\cdot f_{i-1}\)\((a,b)\) 的个数即可。
枚举 \(f\) ,由于斐波那契呈指数增长,所以项数是 \(\log\) 级别。

3.欧拉定理

引入

欧拉定理

\(a^{\varphi(p)}\equiv 1 \pmod p\)
需要满足 \(a\perp p\).
\(a^b\equiv a^{b \mod \varphi(p)}\pmod p\)
说明 \(a^b \mod p\) 是以 \(\varphi(p)\) 为循环的。(不一定是最小循环节)

证明:link

欧拉定理可以求逆元。 \(x^{\varphi(p)-1}\equiv x^{-1} \pmod p\) .

扩展欧拉定理

\(b> \varphi(p)\) 则,\(a^b\equiv a^{b\mod \varphi(p)+\varphi(p)}\pmod p\)
否则 \(a^b\equiv a^b\pmod p\)
对任意 \(a,p\) 都满足。
\(a^b \mod p\) 是经过 \(\varphi(p)\) 才进入循环的。

应用

P4139 上帝与集合的正确用法

\(2^{2^{2...}} \mod p\)
设上面这个式子为 \(x\).
\(x=2^{(2^{2...} \mod \varphi(p)+\varphi(p))}\)
在设 \(y=2^{2...} \mod \varphi(p)\)
我们再求 \(y\).
以此类推,我们最终发现,模数会为 1.
那后面就不用算了。

可以证明 \(p=\varphi(p)\),最多只会更新 \(\log p\) 次。

code
#include<bits/stdc++.h>
using namespace std;
const int N=1e7+10;
int t,p,phi[N],prime[N],tot;
bool v[N];
void init() {
	phi[1]=1;
	for(int i=2; i<N; i++) {
		if(!v[i]) prime[++tot]=i,phi[i]=i-1;
		for(int j=1; j<=tot&&i*prime[j]<N; j++) {
			v[prime[j]*i]=1;
			if(i%prime[j]==0) {phi[i*prime[j]]=phi[i]*prime[j]; break;}
			phi[i*prime[j]]=phi[i]*phi[prime[j]];
		}
	}
}
int power(int a,int b,int p) {
	int res=1;
	for(; b; b>>=1) {
		if(b&1) res=1ll*res*a%p;
		a=1ll*a*a%p;
	}
	return res;
}
int solve(int p) {
	if(p==1) return 0;
	return power(2,solve(phi[p])+phi[p],p);
}
int main() {
	init();
	scanf("%d",&t);
	for(; t; t--) scanf("%d",&p),printf("%d\n",solve(p));
	return 0;
}
P3934 [Ynoi2016] 炸脖龙 I

求区间 \([l,r]\),中, \(a_l^{a_{l+1}^{...a_r}}\)
带修改。

考虑扩展欧拉定理。
跟上题类似。
迭代到 \(p=\varphi(p)\)\(p=1\) 时即可。
注意我们不能直接套用 \(a^b\equiv a^{b\mod \varphi(p)+\varphi(p)}\pmod p\).
因为 \(b\) 可能不大于 \(p\).
所以加个特判。

由于我们只用单点查询,所以修改用一个差分树状数组即可。

code
#include<bits/stdc++.h>
using namespace std;
using LL=long long;
const LL N=5e5+5;
const LL M=2e7+5;
LL n,m;
LL a[N],c[N];
LL phi[M],prime[N*10],tot;
LL st[N];
bool isp[M],flag;
void add(LL p,LL x){
	for(; p<=n; p+=p&-p) c[p]+=x;
}
LL ask(LL p) {
	LL res=0;
	for(; p; p-=p&-p) res+=c[p];
	return res;
}
void init() {
	memset(isp,true,sizeof(isp));
	phi[1]=1;
	for(LL i=2; i<M; i++){
		if(isp[i]) prime[++tot]=i,phi[i]=i-1;
		for(LL j=1; j<=tot&&i*prime[j]<M; j++){
			isp[i*prime[j]]=false;
			if(i%prime[j]==0) {
				phi[i*prime[j]]=phi[i]*prime[j];
				break;
			}
			else phi[i*prime[j]]=phi[i]*(prime[j]-1);
		}
	}
}
LL Qpow(LL a,LL b,LL mod) {
	LL res=1;
	flag=false;
	if(a>=mod) flag=true,a%=mod;
	for(; b; b>>=1) {
		if(b&1) {
			res=res*a;
			if(res>=mod) flag=true,res%=mod;
		}
		a=a*a;
		if(a>=mod) flag=true,a%=mod;
	}
	return res;
}
signed main() {
	init();
	scanf("%lld%lld",&n,&m);
	for(LL i=1; i<=n; i++) scanf("%lld",&a[i]); 
	for(LL i=1; i<=n; i++) add(i,a[i]-a[i-1]);
	for(; m; m--) {
		LL opt,x,y,p;
		scanf("%lld%lld%lld%lld",&opt,&x,&y,&p);
		if(opt==1) {
			add(x,p);
			add(y+1,-p);
		} else {
			LL now=x;
			st[x]=p;
			p=phi[p]; 
			while(p>1&&now<y) {
				st[++now]=p;
				p=phi[p];
			}
			LL res=1;
			for(LL i=now; i>=x; i--) {
				res=Qpow(ask(i),res,st[i]);
				if(flag) res+=st[i];
			}
			printf("%lld\n",res%st[x]);
		}
	}
	return 0;
}
P3747 [六省联考 2017] 相逢是问候

套一个小清新线段树。
需要用到光速乘。

code
#include<bits/stdc++.h>
using namespace std;
using LL=long long;
const int N=5e4+5,M=2e4;
int n,m,P,c,mod[60],pw[60][M+5],pwr[60][M+5],cnt;
int phi(int n) {
	int ret=n;
	for(int i=2; i*i<=n; i++){
		if(n%i) continue;
		ret=1ll*ret*(i-1)/i;
		while(n%i==0) n/=i;
	}
	if(n>1) ret=1ll*ret*(n-1)/n;
	return ret;
}
int Mod(LL x,int p) {
	return x>mod[p]?(x%mod[p]+mod[p]):x;
}
int Fpow(int b,int i) {
	return Mod(1ll*pw[i][b/M]*pwr[i][b%M],i);
}
int calc(int x,int y,int p) {
	if(!y) return Mod(x,p);
	if(p==cnt) return 1;
	return Mod(Fpow(calc(x,y-1,p+1),p),p);
}
void init() {
	mod[0]=P;
	while(mod[cnt]>1) {
		cnt++; mod[cnt]=phi(mod[cnt-1]);
	}
	mod[++cnt]=1;
	for(int i=0; i<=cnt; i++) {
		pw[i][0]=pwr[i][0]=1;
		for(int j=1; j<=M; j++) pwr[i][j]=Mod(1ll*pwr[i][j-1]*c,i);
		for(int j=1; j<=M; j++) pw[i][j]=Mod(1ll*pw[i][j-1]*pwr[i][M],i);
	}
}
int a[N][60],dat[N<<2],t[N<<2];
void pushup(int p) {
	t[p]=min(t[p<<1],t[p<<1|1]);
	dat[p]=(dat[p<<1]+dat[p<<1|1])%P;
}
void build(int p,int l,int r) {
	if(l==r) return dat[p]=a[l][0],void();
	int mid=(l+r)>>1;
	build(p<<1,l,mid);
	build(p<<1|1,mid+1,r);
	pushup(p);
}
void modify(int p,int l,int r,int L,int R) {
	if(t[p]>=cnt) return;
	if(l==r) return dat[p]=a[l][++t[p]],void();
	int mid=(l+r)>>1;
	if(L<=mid) modify(p<<1,l,mid,L,R);
	if(R>mid) modify(p<<1|1,mid+1,r,L,R);
	pushup(p);
}
int query(int p,int l,int r,int L,int R) {
	if(L<=l&&r<=R) return dat[p];
	int mid=(l+r)>>1,ret=0;
	if(L<=mid) ret=(ret+query(p<<1,l,mid,L,R))%P;
	if(R>mid) ret=(ret+query(p<<1|1,mid+1,r,L,R))%P;
	return ret;
}
int main(){
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	cin>>n>>m>>P>>c;
	init();
	for(int i=1; i<=n; i++) {
		cin>>a[i][0];
		for(int j=1; j<=cnt; j++) a[i][j]=calc(a[i][0],j,0)%P;
	}
	build(1,1,n);
	for(int i=1,op,l,r; i<=m; i++){
		cin>>op>>l>>r;
		if(op) printf("%d\n",query(1,1,n,l,r));
		else modify(1,1,n,l,r);
	}
	return 0;
}

4.Lucas 定理

引入

我们求解组合数时,如果模数较小,我们就比较难求。
因为我们要用阶乘,而超过模数的阶乘会变成 0.

Lucas 定理: \(C_{n}^{m}=C_{\left \lfloor n/p \right \rfloor }^{\left \lfloor m/p \right \rfloor} \cdot C_{n \bmod p}^{m \bmod p}\)

code
#include<bits/stdc++.h>
using namespace std;
using LL=long long;
const int N=1e5+10;
LL n,m,p,t,inv[N],mul[N],mul2[N];
LL C(LL a,LL b) {
	if(a<b) return 0; 
	return mul[a]*mul2[a-b]%p*mul2[b]%p;
}
LL lucas(LL a,LL b) {
	if(a<p&&b<p) return C(a,b);
	return C(a%p,b%p)*lucas(a/p,b/p)%p;
}
signed main() {
	scanf("%lld",&t);
	for(; t; t--) {
		scanf("%lld%lld%lld",&n,&m,&p);
		inv[0]=mul[0]=mul2[0]=1;
		inv[1]=mul[1]=mul2[1]=1;
		for(int i=2; i<p; i++) inv[i]=((-(p/i)*inv[p%i])%p+p)%p;
		for(int i=2; i<p; i++) mul[i]=mul[i-1]*i%p;
		for(int i=2; i<p; i++) mul2[i]=mul2[i-1]*inv[i]%p;
		printf("%lld\n",lucas(n+m,n));
	}
	return 0;
}

注意特判:递归过程中可能会出现 \(n<m\) 返回 0,那么整个式子就是 0.

证明:link

应用

P4345 [SHOI2015]超能粒子炮·改

\(f(n,k)\)\(\sum ^k_{i=0} C_n^i\)
\(f(n,k)\bmod p,p=2333\).

我们尝试用 Lucas 定理形式拆解。
\(C_{n}^{m}=C_{n/p}^{m/p} \cdot C_{n \bmod p}^{m \bmod p}\)
\(f(n,k) = C_{n/p}^{0/p}*C_{n \bmod p}^{0\bmod p}+C_{n/p}^{1/p}*C_{n \bmod p}^{1\bmod p}+....\)
我们发现可以按 \(C_{n/p}^{i/p}\) 分组.

\(f(n,k)= \sum_j^{k/p-1} (C_{n/p}^j * f(n\bmod p,p-1)) + C_{n/p}^{k/p}*f(n\bmod p,k\bmod p)\)
\(=f(n/p,k/p-1)*f(n\bmod p,p-1) + C_{n/p}^{k/p}*f(n\bmod p,k\bmod p)\)

code
#include<bits/stdc++.h>
using namespace std;
using LL=long long;
const LL p=2333;
LL t,n,k;
LL f[p+10][p+10],c[p+10][p+10];
LL C(LL n,LL m) {
	if(n<p&&m<p) return c[n][m];
	return C(n%p,m%p)*C(n/p,m/p)%p;
}
LL solve(LL n,LL k) {
	if(!n) return 1;
	if(!k) return 1;
	if(k<0) return 0;
	if(n<p&&k<p) return f[n][k];
	return (solve(n%p,p-1)*solve(n/p,k/p-1)+C(n/p,k/p)*solve(n%p,k%p))%p;
}
signed main() {
	scanf("%lld",&t);
	for(int i=0; i<p; i++) f[i][0]=c[i][0]=1;
	for(int i=1; i<p; i++) 
		for(int j=1; j<p; j++)
			c[i][j]=(c[i-1][j]+c[i-1][j-1])%p,f[i][j]=(f[i][j-1]+c[i][j])%p;
	for(; t; t--) {
		scanf("%lld%lld",&n,&k);
		printf("%lld\n",solve(n,k));
	}
	return 0;
}

5.中国剩余定理

引入

中国剩余定理

可以求解同余方程组:

\( \begin{cases} x \equiv a_1 \pmod {p_1} \\ x \equiv a_2 \pmod {p_2} \\ x \equiv a_3 \pmod {p_3} \\ \end{cases} \)

要求 \(p\) 两两互质。

\(M=\prod p,M_i=M/p_i\)
求出 \(t_i=M_i^{-1} \pmod {p_i}\)
\(ans= \sum a_i t_i M_i\)

扩展中国剩余定理:

\(p\) 不互质也可以。

考虑一一合并所有问题。

已知

\( \begin{cases} x \equiv a_1 \pmod {p_1} \\ x \equiv a_2 \pmod {p_2} \\ \end{cases} \)

\(x=k_1p_1 + a_1=k_2p_2 + a_2\).
移项 \(k_1p_1-k_2p_2 = -a_1+a_2\).
那么我们发现这是经典不定方程形式,扩欧求解出 \(k_1,k_2\),或判断无解。
若有解,利用 \(k_1,k_2\) 求出 \(x^*\) 为一个特解。
\(x \equiv x^* \pmod {lcm (p_1,p_2)}\)

code
#include<bits/stdc++.h>
using namespace std;
using LL=long long;
LL n,p1,c1,p2,c2;
LL mul(LL a,LL b,LL mod) {
    LL res=0;
    for(; b; b>>=1) {
        if(b&1) res=(res+a)%mod;
        a=(a+a)%mod;
    }
    return res;
}
LL gcd(LL a,LL b) {
	if(b==0) return a;
	return gcd(b,a%b);
} 
void exgcd(LL a,LL b,LL &x,LL &y) {
	if(b==0) {x=1; y=0; return ;}
	exgcd(b,a%b,x,y);
	int z=x; x=y; y=z-(a/b)*y;
}
signed main() {
	ios::sync_with_stdio(false);
	cin>>n;
	c1=0; p1=1;
	for(int i=1; i<=n; i++) {
		cin>>p2>>c2;
		LL d=gcd(p1,p2),lc=p1/d*p2,k,y;
		c2=(c2-c1%p2+p2)%p2;
		exgcd(p1,p2,k,y);
		k=(k%(p2/d)+p2/d)%(p2/d);
		k=mul(k,c2/d,p2);
		c1=(c1+mul(p1,k,lc))%lc;
		p1=lc;
	}
	cout<<c1<<endl;
	return 0;
}

应用

P2480 [SDOI2010]古代猪文

\(g^{\sum d|n C_n^d} \bmod 999911659\).
考虑欧拉定理:由于 999911659 是质数,所以求 \(\sum d|n C_n^d \bmod 999911658\).
我们直接 Lucas 会爆炸,所以观察发现 \(999911658 = 2*3*4697*35617\)
所以我们对这四个质数一一做一遍 Lucas ,再用中国剩余定理合并即可。

code
#include<bits/stdc++.h>
using namespace std;
const int N=36666;
using LL=long long;
LL p[4]={2,3,4679,35617},c[4];
LL inv[N],mul1[N],mul2[N];
LL n,g;
LL M[4],M0=999911658,t[4],ans;
LL mod=999911659;
vector<LL> s;
LL power(LL a,LL b,LL p) {
	LL res=1;
	for(; b; b>>=1) {
		if(b&1) res=res*a%p;
		a=a*a%p;
	}
	return res;
}
LL C(LL n,LL m,LL p) {
	if(n<m) return 0;
	return mul1[n]*mul2[n-m]%p*mul2[m]%p;
}
LL Lucas(LL n,LL m,LL p) {
	if(n<p&&m<p) return C(n,m,p);
	return C(n%p,m%p,p)*Lucas(n/p,m/p,p)%p;
}
signed main() {
	scanf("%lld%lld",&n,&g);
	if(g%mod==0) return puts("0"),0;
	for(int d=1; d*d<=n; d++) {
		if(n%d==0) {
			s.push_back(d);
			if(n/d!=d) s.push_back(n/d);
		}
	}
	for(int k=0; k<4; k++) {
		inv[0]=mul1[0]=mul2[0]=1;
		inv[1]=mul1[1]=mul2[1]=1;
		for(int i=2; i<p[k]; i++) inv[i]=-(p[k]/i)*inv[p[k]%i]%p[k];
		for(int i=2; i<p[k]; i++) mul1[i]=mul1[i-1]*i%p[k];
		for(int i=2; i<p[k]; i++) mul2[i]=mul2[i-1]*inv[i]%p[k];
		for(int i=0; i<(int)s.size(); i++) {
			c[k]=(c[k]+Lucas(n,s[i],p[k]))%p[k];
		}
		c[k]=(c[k]%p[k]+p[k])%p[k];
		M[k]=M0/p[k];
		t[k]=power(M[k],p[k]-2,p[k]);
		ans=ans+M[k]*t[k]*c[k];
	}
	ans=(ans%M0+M0)%M0;
	printf("%lld\n",power(g,ans,mod));
	return 0;
}
P4774 [NOI2018] 屠龙勇士

题意复杂,难以概括。

先用平衡树维护出每次攻击的武器。
然后跑一个扩展中国剩余定理即可。

记得用龟速乘或“骆可强”快速乘防止溢出。

code
#include<bits/stdc++.h>
using namespace std;
using LL=long long;
const int N=1e5+10;
multiset<LL> st;
int T,n,m;
LL s[N],p[N],b[N],z[N],a[N];
LL Mul(LL a,LL b,LL p) {
	LL res=0;
	for(; b; b>>=1) {
		if(b&1) res=(res+a)%p;
		a=(a+a)%p;
	}
	return res;
}
LL gcd(LL a,LL b) {
	if(b==0) return a;
	return gcd(b,a%b);
}
void exgcd(LL a,LL b,LL &x,LL &y) {
	if(b==0) {x=1; y=0; return ;}
	exgcd(b,a%b,x,y);
	LL z=x; x=y; y=z-(a/b)*y;
}
void solve() {
	st.clear();
	cin>>n>>m;
	for(int i=1; i<=n; i++) cin>>s[i];
	for(int i=1; i<=n; i++) cin>>p[i];
	for(int i=1; i<=n; i++) cin>>b[i];
	for(int i=1; i<=m; i++) cin>>z[i],st.insert(z[i]);
	for(int i=1; i<=n; i++) {
		auto it=st.upper_bound(s[i]);
		if(it==st.begin()) a[i]=*st.begin(),st.erase(st.begin());
		else a[i]=*(--it),st.erase(it);
		st.insert(b[i]);
	}
	LL p0=1,c0=0; // x = p0*k + c0
	for(int i=1; i<=n; i++) { // a*x = s (mod p)
		LL fa=a[i],fp=p[i],fs=s[i];
		LL d=gcd(fa,fp),x,y;
		if(fs%d!=0) return printf("-1\n"),void();
		fs/=d;
		exgcd(fa/=d,fp/=d,x,y);
		x=(x%fp+fp)%fp;
		fs=Mul(fs,x,fp); // x = fs (mod fp)
		// x = p0 * k + c0= fp * y + fs -> p0*k-fp*y=fs-c0
		d=gcd(p0,fp);
		if((fs-c0)%d!=0) return printf("-1\n"),void();
		exgcd(p0,fp,x,y);
		x=x*((fs-c0)/d); y=y*((fs-c0)/d);
		LL lc=p0/d*fp; 
		c0=(Mul(x,p0,lc)+c0)%lc;
		p0=lc;
	}
	LL idx=1;
	for(int i=2; i<=n; i++) {
		if(ceil((double)s[i]/a[i])>ceil((double)s[idx]/a[idx]))
			idx=i;
	}
	while(c0<ceil((double)s[idx]/a[idx])) c0+=p0;
	printf("%lld\n",c0);
}
int main() {
	ios::sync_with_stdio(false);
	cin>>T;
	for(; T; T--) solve();
	return 0;
}

6.BSGS

引入

BSGS:

求解高次同余方程 \(a^x \equiv b \pmod p (a \perp p)\).
注意到 \(x<p\).
设 $t=\left \lceil \sqrt p \right \rceil $

我们知道任意 \(x<p\),都可以表示为 \(x=it-j (i,j\le t)\)
\(a^x\equiv a^{it-j}\equiv b \pmod p\).
那么 \(a^{it} \equiv b * a^j \pmod p\)

预处理出所有 \(b * a^j\),将它们存在哈希表里,或 STL map 里。
再处理出所有 \(a^{it}\),判断是否存在有 \(b*a^j = a^{it}\).
答案为 \(it-j\).

code
#include<bits/stdc++.h>
using namespace std;
using LL=long long;
LL p,a,b;
unordered_map<LL,LL> hsh;
LL Qpow(LL a,LL b,LL p) {
	LL res=1;
	for(; b; b>>=1) {
		if(b&1) res=res*a%p;
		a=a*a%p;
	}
	return res;
}
LL bsgs(LL p,LL a,LL b) {
	hsh.clear();
	b%=p;
	LL t=sqrt(p)+1;
	for(int i=0; i<t; i++) {
		LL s=b*Qpow(a,i,p)%p;
		hsh[s]=i;
	}
	a=Qpow(a,t,p);
	for(int i=1; i<=t; i++) {
		LL val=Qpow(a,i,p);
		int j=hsh.find(val)==hsh.end()?-1:hsh[val];
		if(j>=0&&i*t-j>=0) return i*t-j;
	}
	return -1;
}
signed main() {
	scanf("%lld%lld%lld",&p,&a,&b);
	LL ans=bsgs(p,a,b);
	if(ans==-1) puts("no solution");
	else printf("%lld\n",ans);
	return 0;
}

笔者发现, BSGS 似乎与 meet in middle 些许相似。

扩展 BSGS:

处理 \(a\not \perp p\) 情况.
\(p,b\) 不断除以 \(gcd(a,p)\),直到 \(a\perp p\).
若期间 \(b\) 不能整除,则无解。
设总共除了 \(d\),除了 \(cnt\) 次.
则转换为 \(a^x * a^{cnt} = \frac{b}{d} \pmod {\frac{p}{d}}\)
答案为 \(x+cnt\).

code
#include<bits/stdc++.h>
using namespace std;
int p,a,b;
map<int,int> hsh;
int Qpow(int a,int b,int p) {
	int res=1;
	for(; b; b>>=1) {
		if(b&1) res=1ll*res*a%p;
		a=1ll*a*a%p;
	}
	return res;
}
int gcd(int a,int b) {
	if(b==0) return a;
	return gcd(b,a%b);
}
int bsgs(int a,int b,int p,int ad) {
	hsh.clear();
	int t=sqrt(p)+1,s=1;
	for(int i=0; i<t; i++,s=1ll*s*a%p) hsh[1ll*s*b%p]=i;
	int tmp=s; s=ad;
	for(int i=0; i<=t; i++,s=1ll*s*tmp%p)
		if(hsh.find(s)!=hsh.end())
			if(i*t-hsh[s]>=0) return i*t-hsh[s];
	return -1;
}
int exbsgs(int a,int b,int p) {
	a%=p; b%=p;
	if(b==1||p==1) return 0;
	int cnt=0,d,ad=1;
	for(; (d=gcd(a,p))!=1; ) {
		if(b%d) return -1;
		cnt++; b/=d; p/=d;
		ad=(1ll*ad*a/d)%p;
		if(ad==b) return cnt; 
	}
	int ans=bsgs(a,b,p,ad);
	if(ans==-1) return -1;
	return ans+cnt;
}
signed main() {
	for(; ; ) {
		scanf("%d%d%d",&a,&p,&b);
		if(!a&&!b&&!p) break;
		int ans=exbsgs(a,b,p);
		if(ans==-1) puts("No Solution");
		else printf("%d\n",ans);
	}
	return 0;
}

应用

P3306 [SDOI2013] 随机数生成器

矩阵乘法的 BSGS.

code
#include<bits/stdc++.h>
using namespace std;
int p,a,b,x1,u;
struct Mtr {
	int n,m;
	int a[3][3];
	bool operator < (const Mtr Q) const {
		return a[1][1]<Q.a[1][1];
	}
} X,F,S,I;
map<Mtr,int> hsh;
Mtr Mul(Mtr A,Mtr B) {
	Mtr C; C.n=A.n; C.m=B.m;
	for(int i=1; i<=C.n; i++)
		for(int j=1; j<=C.m; j++)
			C.a[i][j]=0;
	for(int i=1; i<=A.n; i++)
		for(int j=1; j<=B.m; j++)
			for(int k=1; k<=A.m; k++)
				C.a[i][j]=(C.a[i][j]+1ll*A.a[i][k]*B.a[k][j])%p;
	return C;
}
void solve() {
	hsh.clear();
	scanf("%d%d%d%d%d",&p,&a,&b,&x1,&u);
	if(x1==u) {
		printf("1\n");
		return ;
	}
	if(a==0) {
		if(x1==u) printf("1\n");
		else if(b==u) printf("2\n");
		else printf("-1\n");
		return ;
	}
	if(a==1&&b==0) {
		if(x1==u) printf("1\n");
		else printf("-1\n");
		return ;
	}
	I.n=I.m=2; I.a[1][1]=I.a[2][2]=1; 
	X.n=X.m=2; X.a[1][1]=a; X.a[1][2]=b; X.a[2][1]=0; X.a[2][2]=1;
	F.n=2; F.m=1; F.a[1][1]=x1; F.a[2][1]=1;
	S.n=2; S.m=1; S.a[1][1]=u; S.a[2][1]=1;
	int t=sqrt(p)+1;
	Mtr G=I;
	for(int i=0; i<t; i++) {
		Mtr J=Mul(G,S);
		hsh[J]=i;
		G=Mul(G,X);
	}
	Mtr T=I;
	for(int i=0; i<=t; i++) {
		Mtr J=Mul(T,F);
		if(hsh.find(J)!=hsh.end()) {
			if(-hsh[J]+i*t>=0) {
				printf("%d\n",-hsh[J]+i*t+1); 
				return ;
			}
		}
		T=Mul(T,G);
	}
	printf("-1\n");
	return ;
} 
int main() { 
	int T; scanf("%d",&T);
	for(; T; T--) solve();
	return 0;
}
posted @ 2023-04-23 21:01  s1monG  阅读(23)  评论(0编辑  收藏  举报