初等数论——整除相关
前置
整除定义: 如果
性质 1:
性质 2:
素数
定义: 质数是指在大于1的自然数中,除了1和它本身以外不再有其他因数的自然数。
定理 1: 在自然数集中,小于
素数的判定
试除法,时间复杂度:
实现方法:设
code:
bool check(int x)
{
if(x<2)return 0;
for(int i=2;i<=sqrt(x);i++)
if(x%i==0)return 0;
return 1;
}
素数筛法
1.埃氏筛
算法原理:如果
我们可以优化一下上面的算法,从小到大枚举,把每一个质数的倍数都筛掉。
code:
void prime(int n)
{
for(int i=2;i<=n;i++)
{
if(vis[i])continue;
p[++m]=i;
for(int j=1;j*i<=n;j++)
vis[j*i]=1;
}
}
时间复杂度:
2.欧拉筛
埃氏筛法即使在优化后任然会重复标记合数,即每个合数都可能被筛去多次。比如
的方式。
欧拉筛是埃氏筛的改进,我们保证每个合数只会被它的最小质因子筛掉。
根据算术基本定理(唯一分解定理),任何大于1的自然数,都可以唯一分解成有限个,质数的乘积的形式,且经过适当排序其写法唯一。(在下一节会较详细的讲解)。也就是说,任何大于1的自然数至少有一个素数因子。
快速线性筛法的原理是利用每个数 i 的最小素因子来筛素数,即筛去
时间复杂度:
线性筛的部分过程图。
code:
#include<bits/stdc++.h>
using namespace std;
const int N=200000000+10;
int n,q,m;
int prime[N];
bool vis[N];
int main()
{
scanf("%d%d",&n,&q);
for(int i=2;i<=n;i++)
{
if(!vis[i])prime[++m]=i;
for(int j=1;j<=m,prime[j]*i<=n;j++)
{
vis[prime[j]*i]=1;
if(i%prime[j]==0)break;
}
}
for(int i=1;i<=q;i++)
{
int x;
scanf("%d",&x);
printf("%d\n",prime[x]);
}
return 0;
}
欧拉筛在后面筛其他数时也有一定关联,这将会放在第四章筛法中讲解。
例题:
约数
唯一分解定理
定义:任何一个大于
其中
正约数个数为:
正约数之和为:
分解质因数
试除法:与判质数的试除法相似,只要一个数是
时间复杂度:
code:
void fj(int x)
{
for(int i=2;i*i<=x;i++)
{
if(x%i==0)
{
fc[++cnt]=i;
while(x%i==0)x/=i,num[i]++;
}
}
if(x>1)fc[++cnt]=x,num[x]++;
for(int i=1;i<=cnt;i++)
cout<<fc[i]<<" "<<num[fc[i]]<<endl;
}
最小质因子法: 依托欧拉筛可以求出每个数的最小质因子,可以直接除以数的最小质因子来分解质因数。
code
void fj(int x)
{//s[]是最小质因子
for(int i=2;i<=1000;i++)
{
if(!vis[i])prime[++m]=i,s[i]=i;
for(int j=1;j<=m,prime[j]*i<=1000;j++)
{
vis[prime[j]*i]=1;
s[prime[j]*i]=prime[j];
if(i%prime[j]==0)break;
}
}
while(x>1)
{
if(!num[s[x]])fc[++cnt]=s[x];
num[s[x]]++;
x/=s[x];
}
for(int i=1;i<=cnt;i++)
cout<<fc[i]<<" "<<num[fc[i]]<<"\n";
}
求约数集合
试除法:求单个数的约数集合。
与判质数的试除法相似,显然约数是成对出现,所以每次加入集合时要加两次。
时间复杂度:
code:
vector<int>res;
void factor(int n)
{
for(int i=1;i*i<=n;i++)
{
if(n%i!=0)continue;
res.push_back(i);
if(n/i!=i)res.push_back(n/i);
}
}
倍数法:求
与埃氏筛相似,枚举每一个数的两个约数。
时间复杂度近似于:
code:
vector<int>res[N];
void factor(int n)
{
for(int i=1;i<=n;i++)
{
for(int j=1;i*j<=n;j++)
res[j*i].push_back(j);
}
}
例题:
最大公约数与最小公倍数
定义:
两个数
两个数
性质1:
性质2:
性质3:
性质4:
性质5:
性质6:
性质7:
性质8:
求解
1.辗转相除法(欧几里得法)
证明:
时间复杂度:
code:
int \gcd(int a,int b)
{
if(!b) return a;
return \gcd(b,a%b);
}
2.更相减损术
证明:设
这个方法时间复杂度有些低下,所以我们要进行优化。
根据性质5,可以想到如果两个数可以快速的找到两者的公因数,就可以一定的减少计算量,从而降低时间复杂度。最容易判断的是:是否都为
这个方法改进后还是不如上一个方法,在一般的求解时不会用它,但在需要求两个大数(要写高精)的最大公因数时,就比上一个方法好了。
P2152 [SDOI2009] SuperGCD(高精\gcd模板)
code:
#include<bits/stdc++.h>
#define N 10009
using namespace std;
int x[9][N],l[9],cnt;
bool dayu(int a,int b){
if(l[a]!=l[b])return l[a]>l[b];
for(int i=l[a];~i;i--)
if(x[a][i]!=x[b][i]) return x[a][i]>x[b][i];
return 0;
}
int pl(int a,int b){
l[a]=max(l[a],l[b]);
x[a][l[a]]=0;
for(int i=0;i<l[a];i++)x[a][i]+=x[b][i];
for(int i=0;i<l[a];i++)
x[a][i+1]+=x[a][i]/10,x[a][i]%=10;
if(x[a][l[a]]) l[a]++;
return a;
}
int mi(int a,int b){
for(int i=0;i<l[a];i++){
if(x[a][i]<x[b][i])
x[a][i]+=10,x[a][i+1]--;
x[a][i]-=x[b][i];
}
while(!x[a][l[a]-1])
if(l[a]) l[a]--;
else break;
return a;
}
int mu2(int a){
for(int i=0;i<l[a];i++)x[a][i]<<=1;
for(int i=0;i<l[a];i++)
x[a][i+1]+=x[a][i]/10,x[a][i]%=10;
if(x[a][l[a]]) l[a]++;
return a;
}
int de2(int a){
for(int i=l[a]-1;i;i--)
x[a][i-1]+=(x[a][i]&1)*10,
x[a][i]>>=1;
x[a][0]>>=1;
if(!x[a][l[a]-1]) l[a]--;
return a;
}
char as[N],bs[N];
int seize(int a,int b){
if(!l[b]) return a;
if((x[a][0]&1)==1&&(x[b][0]&1)==1){
if(dayu(b,a)) swap(a,b);
return seize(b,mi(a,b));
}
if((x[a][0]&1)==0&&(x[b][0]&1)==0)
return mu2(seize(de2(a),de2(b)));
if((x[a][0]&1)==0) a=de2(a);
if((x[b][0]&1)==0) b=de2(b);
return seize(a,b);
}
int main(){
int a=++cnt,b=++cnt,c;
scanf("%s%s",&as,&bs);
l[a]=strlen(as);
l[b]=strlen(bs);
for(int i=0;i<l[a];i++) x[a][l[a]-1-i]=as[i]-'0';
for(int i=0;i<l[b];i++) x[b][l[b]-1-i]=bs[i]-'0';
c=seize(a,b);
for(int i=l[c]-1;~i;i--)
printf("%d",x[c][i]);
return 0;
}
欧拉函数
定义:
性质1 当
性质2: 设
性质3: 设
性质4: 当
性质5:
性质6: 若
欧拉函数的通项公式:
证明:若
由唯一分解定理可知:
又因为欧拉函数是积性函数
求解欧拉函数
求单个数的欧拉函数时,枚举质因数按照公式求就可以了,时间复杂度:
求解
phi[1]=1;
for(int i=2;i<=n;i++)
{
if(!vis[i])p[++m]=i,phi[i]=i-1;
for(int j=1;j<=m,p[j]*i<=n;j++)
{
vis[i*p[j]]=1;
if(i%p[j]==0)
{
phi[i*a[j]]=phi[i]*p[j];
break;
}
else phi[i*p[j]]=phi[i]*(p[j]-1);
}
}
一个点
code:
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int n,m;
int f[N],a[N];
bool vis[N];
long long ans;
int main()
{
scanf("%d",&n);
if(n==1)
{
puts("0");
return 0;
}
for(int i=2;i<=n;i++)
{
if(!vis[i])a[++m]=i,f[i]=i-1;
for(int j=1;j<=m,a[j]*i<=n;j++)
{
vis[i*a[j]]=1;
if(i%a[j]==0)
{
f[i*a[j]]=f[i]*a[j];
break;
}
else f[i*a[j]]=f[i]*(a[j]-1);
}
}
for(int i=2;i<=n-1;i++)
ans+=f[i];
cout<<ans*2+3;
return 0;
}
欧拉反演
证明:令
用途:求解一系列最大公约数的和。
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int n,p[N],m,phi[N];
bool vis[N];
int main()
{
cin>>n;
phi[1]=1;
for(int i=2;i<=n;i++)
{
if(!vis[i]) p[++m]=i,phi[i]=i-1;
for(int j=1;j<=m&&i*p[j]<=n;j++)
{
vis[i*p[j]]=1;
if(i%p[j]==0)
{
phi[i*p[j]]=p[j]*phi[i];
break;
}
phi[i*p[j]]=phi[p[j]]*phi[i];
}
}
long long ans=0;
for(int i=1;i<=n;i++)
ans+=1ll*(n/i)*(n/i)*phi[i];
cout<<ans<<"\n";
}
参考资料:
繁凡的数论全家桶
oi-wiki 数论板块
update:2024.11.27
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 实操Deepseek接入个人知识库
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· 易语言 —— 开山篇