基础数论(未更新完全)

质数相关算法

01 Miller–Rabin

前置定理:

费马小定理

\(p\) 为素数,\(a\)为整数,且\(a\)\(p\)互素,那么

\[a^{p-1}\equiv 1(\mod p) \]

我们设 \(d\times 2^r=p-1\) ,那么式子就变成了

\[a^{d\times 2^r}\equiv 1(\mod p) \]

二次探测定理

\(p\) 是奇素数,\(a<p\),则 \(a^2\equiv 1(\mod p)\) 的解为

\[a\equiv 1(\mod p) \]

\[a\equiv p-1(\mod p) \]

作者不会证

然后我们把这两个式子放到一起,最佳搭配!

那么对于下面的两个式子,必满足其一

\[a^d\equiv 1(\mod p)\\ 对于0\le i<r,a^{d\times2^i}\equiv -1(\mod p) \]

\(Miller–Rabin\)过程

\(p-1\) 中的因子为 \(2\) 的个数提取出来,设 \(d=p-1\) ,取 \(k\) 个数 \(1<a_1,a_2,a_3…a_k<p\) 带入我们融合出来的公式判断是否构成。

时间复杂度\(O(k\log p)\)

\(code:\)

//#pragma GCC optimize("O2")
#include<bits/stdc++.h>
using namespace std;
long long P[7]={2,3,5,13,17,31,37};
inline long stimes(long long a,long long b,long long mod){
	long long sum=0;
	while(b){
		if(b&1){
			sum=(sum+a)%mod;
		}
		a=(a+a)%mod;
		b>>=1;
	}
	return sum;
}
long long qpow(long long a,long long b,long long mod){
	long long sum=1;
	while(b){
		if(b&1){
			sum=stimes(sum,a,mod)%mod;
		}
		a=stimes(a,a,mod)%mod;
		b>>=1;
	}
	return sum;
}
long long Miller_Rabin(long long x,long long p){
	long long d=x-1,r=0;
	while(!(d&1)){
		r++;
		d>>=1;
	}
	long long sum=qpow(p,d,x);
	if(sum==1){
		return 1;
	}
	for(int i=0;i<r;i++){
		if(sum==x-1){
			return 1;
		}
		sum=stimes(sum,sum,x);
	}
	return 0;
}
inline long is_p(long long x){
	if(x<=1){
		return 0;
	}
	for(int i=0;i<7;i++){
		if(x==P[i]){
			return 1;
		}
		if(x%P[i]==0){
			return 0;
		}
		if(!Miller_Rabin(x,P[i])){
			return 0;
		}
	}
	return 1;
}
int main(){
	int T;
	cin>>T;
	while(T--){
		long long x;
		cin>>x;
		if(is_p(x)){
			puts("YES");
		}else{
			puts("NO");
		}
	}
	return 0;
}

BSGS,原根和阶

01 阶

定义

首先,阶长这样\(\delta _m(a)\)

读作\(a\) %\(m\)意义下的阶。

我们让\(\delta _m(a)=x\)

\(x\)是最小满足

\[\begin{cases} a^0,a^1,a^2…a^{x-1}不等\\ a^x\equiv 1(\mod m) \end{cases} \]

的数。

一些性质

(1)

我们发现:

注意力惊人

\[\delta _n(g)=\varphi(n) \]

(2)

\[\forall a,\delta _m(a)\mid \varphi (m) \]

即任意数的阶都是\(\varphi\)的因数

(3)

\[\delta _m(a^k)=\frac{\delta _m(a)}{\gcd (\delta _m(a),k)} \]

不证了,作者不会

02 原根

定义

有一个数\(n\),

\(g^0,g^1,g^2…g^{\varphi(n)-1}\),不相等,

那么\(g\)\(\mod n\)的原根。

注:

\(\varphi(n)\)表示的是\(\le n\)的数中和\(n\)互质的数的个数。

\(eg:\)

\(n=5\)时,

\(2\)为$ \mod n$的一个原根,

因为

\[2^0\mod 5=1\\ 2^1\mod 5=2\\ 2^2\mod 5=4\\ 2^3\mod 5=3 \]

互不相等。

一些性质

(1)

同时3也是$ \mod 5$的一个原根,

所以5有两个原根。

据此,我们发现:

又来

如果\(p\)是一个质数,那么\(p\)的原根数为\(\varphi(\varphi(p))\)

(2)

只有形如:

\[2,4,p^k,2p^k \]

的数有原根。

03 BSGS算法

即大步小步算法,常用于求解离散对数问题,说白了就是\(O(\sqrt p)\)的时间复杂度下解决\(a^x\equiv b(\mod p)\)\(x\)的值的算法,要求\(a\)\(p\)互质。

做法:

首先令\(x=A\left \lceil \sqrt p \right \rceil -B\),其中\(0\le A,B\le \left \lceil \sqrt p \right \rceil\)

把它带入方程中得\(a^{A\left \lceil \sqrt p \right \rceil -B}\equiv b(\mod p)\)

进行指数分解和移项后得\(a^{A\left \lceil \sqrt p \right \rceil} \equiv ba^B(\mod p)\)

然后我们就可以枚举 \(B\) 的值来计算方程右端的所有取值,并把它们存到哈希表中。

接着枚举 \(A\) 来得到方程左端所有值,找到与右端相同时的 \(A\) ,最后就能运用 \(x=A\left \lceil \sqrt p \right \rceil -B\) 来求出方程的解了。

因为\(0\le A,B\le \left \lceil \sqrt p \right \rceil\) ,所以枚举\(A\)\(B\)的时间复杂度就是\(O(\sqrt p)\)

\(code\):

long long BSGS(long long a,long long b,long long p){
	map<long long,long long>mp;
	long long sum=1,sqrtp=sqrt(p)+1;
	for(int B=1;B<=sqrtp;B++){
		sum=sum*a%p;//a^b的值
		mp[b*sum%p]=B;//存入哈希表
	}
	long long now=sum;
	//sum现在为a^sqrt(p)的值。
	for(int A=1;A<=sqrtp;A++){
		if(mp[now]){
			return (long long)A*sqrtp-mp[now];
		}
		now=now*sum%p;
	}
	return -1;
}

04 例题

01 P3846

P3846 [TJOI2007] 可爱的质数/【模板】BSGS

题意即板子,套板子即可。

\(code:\)

//#pragma GCC optimize("O2")
#include<bits/stdc++.h>
using namespace std;
long long BSGS(long long a,long long b,long long p){
	map<long long,long long>mp;
	long long sum=1,sqrtp=sqrt(p)+1;
	for(int B=1;B<=sqrtp;B++){
		sum=sum*a%p;//a^b的值
		mp[b*sum%p]=B;//存入哈希表
	}
	long long now=sum;
	//sum现在为a^sqrt(p)的值。
	for(int A=1;A<=sqrtp;A++){
		if(mp[now]){
			return (long long)A*sqrtp-mp[now];
		}
		now=now*sum%p;
	}
	return -1;
}
int main(){
	long long a,b,p;
	cin>>p>>a>>b;
	int t=BSGS(a,b,p);
	if(t==-1){
		cout<<"no solution";
	}else{
		cout<<t;
	}
	return 0;
}

莫比乌斯反演

01 狄利克雷卷积

定义:

\(OI\) \(Wiki\)可知:
对于两个数论函数\(f(x)\)\(g(x)\),它们狄利克雷卷积得到的结果\(h(x)\)定义为:

\[h(x)=\sum_{d\mid x}f(d)g\left(\dfrac{x}{d} \right) \]

上述式子简记为:

\[h=f*g \]

性质:

交换律:\(f*g=g*f\)
结合律:\((f*g)*h=f*(g*h)\)
分配率:\((f+g)*h=f*h+g*h\)

02 数论分块

引入:

给定一个\(n\),求:

\[\sum_{i=1}^{n} \left \lfloor \frac{n}{i} \right \rfloor \]

当数据范围较大时,\(O(n)\)会超时,那么可以使用数论分块解决。
数论分块可以快速计算一些含有除法向下取整的和式。

时间复杂度:

\(\left \lfloor \frac{n}{i} \right \rfloor\)的值的集合大小最多为$2\sqrt{n} $。

证明如下

对于正整数 \(i\) ,当 $i\le \sqrt{n} $ 时:
\(i\)最多有\(\sqrt{n}\) 种取值,所以\(\left \lfloor \frac{n}{i} \right \rfloor\)最多只有\(\sqrt{n}\)种取值。
\(i>\sqrt{n}\)时:
因为\(i>n\)时,\(\left \lfloor \frac{n}{i} \right \rfloor\)的值始终为\(0\),而在\(\sqrt{n}<i\le n\)时,\(i\)最多只有\(\sqrt{n}\)种取值,所以\(\left \lfloor \frac{n}{i} \right \rfloor\)最多也只有\(\sqrt{n}\)种取值。
综上所述,\(\left \lfloor \frac{n}{i} \right \rfloor\)最多有\(2\sqrt{n}\)种取值。
证毕。

所以数论分块的时间复杂度为\(O(\sqrt{n})\)

左右边界:

\(\sum_{i=1}^{n} \left \lfloor \frac{n}{i} \right \rfloor\)中,设该块的左边界为\(l\),该块的值为\(\left \lfloor \frac{n}{i} \right \rfloor\),那么该块的右边界\(r\)为$\left \lfloor \frac{n}{ \left \lfloor \frac{n}{i} \right \rfloor}\right \rfloor $

证明如下

还没写

\(code\):

int fk(int n){
	int sum=0;
	int l=1,r;
	while(l<=n){
		r=n/(n/l);
		sum+=n/l*(r-l+1);
		l=r+1;
	}
	return sum;
}

03 一个符号

\[[n=1]=\begin{cases}1\qquad(n=1)\\0\qquad(n\ne 1)\end{cases} \]

04 莫比乌斯反演

引入:

来自\(OI\) \(Wiki\):

OI Wiki我的神!!!

莫比乌斯反演是数论中的重要内容。对于一些函数 \(f(n)\),如果很难直接求出它的值,而容易求出其倍数和或约数和 \(g(n)\),那么可以通过莫比乌斯反演简化运算,求得 \(f(n)\) 的值。

莫比乌斯函数:

\[\mu(n)=\begin{cases}1\qquad\qquad(n=1,即n只有一个质因子)\\0\qquad\qquad(n含有平方因子,即n含有出现次数超过一次的质因子)\\(-1)^k\qquad(n为k个质数乘积,且每个质数次数为一次)\end{cases} \]

运用线性筛求莫比乌斯函数:

//mu存储莫比乌斯函数,smu存储前缀和,vis标记非质数
void get_mu(){
	mu[1]=1;
	for(int i=2;i<=N;i++){
		if(!vis[i]){
			mu[i]=-1;
			P.push_back(i);
		}
		for(int p:P){
			int j=i*p;
			if(j>N){
				break;
			}
			vis[j]=1;
			if(i%p==0){
				mu[j]=0;
			}else{
				mu[j]=-mu[i];
			}
		}
	}
	for(int i=1;i<=N;i++){
		smu[i]=smu[i-1]+mu[i];
	}
}

莫比乌斯函数的性质:

\[\sum_{d\mid n}\mu(d)=\begin{cases} 1\qquad n=1\\ 0\qquad n\ne 1 \end{cases} \]

\(\sum_{d\mid n}\mu (d)=[n=1]=\varepsilon (n)\)\(\mu*1=\varepsilon\)

\(\varepsilon(n)\) 的意思

\(\varepsilon(n)=\begin{cases} 1\qquad 如果n=1\\ 0\qquad 如果n>1\end{cases}\)

反演公式:

\[1*\mu=\varepsilon \]

即:

\[\sum_{d\mid n}\mu(d)=\varepsilon(n) \]

也就是

\[\sum_{d\mid n}\mu (d)=[n=1] \]

还有一个公式是:

\[\sum_{d\mid n}\varphi (d)=n \]

反演结论:

\[\sum_{d\mid\gcd(i,j)}\mu(d)=[\gcd(i,j)=1] \]

05 例题

01 p3455

p3455 [POI 2007] ZAP-Queries

题意其实就是求

\[\sum_{i=1}^n\sum_{j=1}^m[\gcd(i,j)=k] \]

\(k\) 消掉,得

\[\sum_{i=1}^{\left \lfloor \frac{n}{k} \right \rfloor }\sum_{j=1}^{\left \lfloor \frac{m}{k} \right \rfloor }[\gcd(i,j)=1] \]

然后带入莫反,得

\[\sum_{i=1}^{\left \lfloor \frac{n}{k} \right \rfloor }\sum_{j=1}^{\left \lfloor \frac{m}{k} \right \rfloor }\sum_{d\mid \gcd(i,j)}\mu(d) \]

我们让 \(i\)\(j\)\(d\) , 然后我们发现就不用考虑 \(d\) 是不是 \(\gcd(i,j)\) 的因数了,然后我们枚举 \(d\) ,式子就变成了

\[\sum_{d=1}^{n}\sum_{i=1}^{\left \lfloor \frac{n}{kd} \right \rfloor }\sum_{j=1}^{\left \lfloor \frac{m}{kd} \right \rfloor }\mu(d) \]

然后我们注意到里面的两层 \(\sum\)\(\mu(d)\) 没有关系了,把它们提出来

\[\sum_{d=1}^{n}\mu(d)\sum_{i=1}^{\left \lfloor \frac{n}{kd} \right \rfloor }\sum_{j=1}^{\left \lfloor \frac{m}{kd} \right \rfloor }1 \]

然后我们就可以把后面的 \(\sum\) 消掉

\[\sum_{d=1}^{n}\mu(d)\left \lfloor \frac{n}{kd} \right \rfloor \left \lfloor \frac{m}{kd} \right \rfloor \]

然后我们用线性筛把 \(\mu\) 筛出来

最后我们进行数论分块即可。

\(code:\)

//#pragma GCC optimize("O2")
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=50005;
int n;
int mu[100010],vis[100010],smu[100010];
vector<int> P;
void get_mu(){
	mu[1]=1;
	vis[0]=vis[1]=1;
	for(int i=2;i<=N;i++){
		if(!vis[i]){
			mu[i]=-1;
			P.push_back(i);
		}
		for(int p:P){
			int j=i*p;
			if(j>N){
				break;
			}
			vis[j]=1;
			if(i%p==0){
				mu[j]=0;
				break;
			}else{
				mu[j]=-mu[i];
			}
		}
	}
	for(int i=1;i<=N;i++){
		smu[i]=smu[i-1]+mu[i];
	}
}
int fk(int x,int y,int a){
	int ans=0;
	x/=a,y/=a;
	if(x>y){
		swap(x,y);
	}
	int l=1,r;
	while(l<=x){
		r=min(x/(x/l),y/(y/l));
		ans+=(x/l)*(y/l)*(smu[r]-smu[l-1]);
		l=r+1;
	}
	return ans;
}
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	get_mu();
	int T;
	cin>>T;
	while(T--){
		int a,b,k;
		cin>>a>>b>>k;
		cout<<fk(a,b,k)<<"\n";
	}
	return 0;
}
posted @ 2025-07-21 15:02  lbh123  阅读(14)  评论(0)    收藏  举报