数论学习笔记
前言
最近学习了数论,教练要求写博客整理学习内容。写在这里,一方面方便自己以后复习,另一方面给其他人参考。顺带一提,数论真的让人头晕。
部分内容来自百度百科。
数论基础
若
如果
一个整数
模
例:
注意:
素数筛
素数:素数数是指在大于
素数判定:
bool Isprime(int a)
{
if(a==1||a==0)return 0;
for(int i=2;i*i<=a;i++)
if(a%i==0)return 0;
return 1;
}
由于因数是成对出现的,所以只需要枚举到平方根即可,时间复杂度为
当数据范围达到
埃氏筛:要得到自然数
例题:
P3912 素数个数
模板:
int eratosthenes(bool a[],int n)
{
int cnt=n-1;
a[1]=1;
for(int i=2;i*i<=n;i++)
if(!a[i])
{
for(int j=i*2;j<=n;j+=i)
{
if(!a[j])
{
a[j]=1;
cnt--;
}
}
}
return cnt;
}
给出要筛数值的范围
时间复杂度:
欧拉筛:让自然数
例题:
P3383 【模板】线性筛素数
模板:
int n,pri[7000000];
bool a[70000000];
int linear(bool a[],int pri[],int n)
{
int cnt=0;
a[1]=1;
for(int i=2;i<=n;i++)
{
if(!a[i])pri[cnt++]=i;
for(int j=0;j<cnt&&i*pri[j]<=n;j++)
{
if(!a[i*pri[j]])a[i*pri[j]]=1;
if(i%pri[j]==0)break;
}
}
return cnt;
}
引用教练PPT上的一张图:
时间复杂度
欧拉函数
欧拉函数:对正整数
欧拉函数的性质:
求前
int n,pri[1000001],f[1000001];
bool a[1000001];
int linear(bool a[],int pri[],int n)
{
int cnt=0;
a[1]=1;f[1]=1;
for(int i=2;i<=n;i++)
{
if(!a[i])
{
pri[cnt++]=i;
f[i]=i-1;
}
for(int j=0;pri[j]<=n/i;j++)
{
a[i*pri[j]]=1;
if(i%pri[j]==0)
{
f[i*pri[j]]=f[i]*pri[j];
break;
}
f[i*pri[j]]=f[i]*(pri[j]-1);
}
}
return cnt;
}
时间复杂度
欧拉函数的运用
例题:
P2158 [SDOI2008]仪仗队
把题目简化一下,就是求横,纵坐标
所以就推出这个式子:
#include <bits/stdc++.h>
using namespace std;
int n,pri[1000001],f[1000001];
bool a[1000001];
int linear(bool a[],int pri[],int n)
{
int cnt=0;
a[1]=1;f[1]=1;
for(int i=2;i<=n;i++)
{
if(!a[i])
{
pri[cnt++]=i;
f[i]=i-1;
}
for(int j=0;pri[j]<=n/i;j++)
{
a[i*pri[j]]=1;
if(i%pri[j]==0)
{
f[i*pri[j]]=f[i]*pri[j];
break;
}
f[i*pri[j]]=f[i]*(pri[j]-1);
}
}
return cnt;
}
int main()
{
long long ans=0;
scanf("%d",&n);
if(n==1)
{
printf("0");
return 0;
}
linear(a,pri,n-1);
for(int i=1;i<=n-1;i++)
ans+=f[i];
printf("%lld",ans*2+1);
return 0;
}
P1390 公约数的和
这题可以想象成是类似仪仗队那题的矩阵,然后套等差数列求和公式即可。
#include <bits/stdc++.h>
using namespace std;
long long n,pri[100000001],f[100000001];
bool a[100000001];
int linear(bool a[],long long pri[],long long n)
{
int cnt=0;
a[1]=1;f[1]=1;
for(int i=2;i<=n;i++)
{
if(!a[i])
{
pri[cnt++]=i;
f[i]=i-1;
}
for(int j=0;pri[j]<=n/i;j++)
{
a[i*pri[j]]=1;
if(i%pri[j]==0)
{
f[i*pri[j]]=f[i]*pri[j];
break;
}
f[i*pri[j]]=f[i]*(pri[j]-1);
}
}
return cnt;
}
int main()
{
long long ans=0;
scanf("%d",&n);
linear(a,pri,n);
for(int i=1;i<=n;i++)
ans+=(long long)f[i]*((1+n/i)*(n/i)/2);
printf("%lld",ans-(1+n)*n/2);
return 0;
}
欧拉定理
模
模
模
例:
扩展欧几里得算法
给定一个关于未知数
这个式子也被称之为裴蜀等式。
方程有整数解,当且仅当
接下来引用教练PPT上的一幅图:
模板:
int exgcd(int a,int b,int &x,int &y)
{
if(b==0)
{
x=1;
y=0;
return a;
}
int r=exgcd(b,a%b,x,y);
int d=x;
x=y;
y=d-a/b*y;
return r;
}
既可以求出
继续引用教练的PPT:
由于扩展欧几里德算法求出的是通解,所以最后结果需要乘以
例题:
P1082 [NOIP2012 提高组] 同余方程
由
得
然后就转化成了裴蜀等式,扩展欧几里得算法解决。
#include <bits/stdc++.h>
using namespace std;
int n,a,b,x,y;
int exgcd(int a,int b,int &x,int &y)
{
if(b==0)
{
x=1;
y=0;
return a;
}
int r=exgcd(b,a%b,x,y);
int d=x;
x=y;
y=d-a/b*y;
return r;
}
int main()
{
scanf("%d%d",&a,&b);
int r=exgcd(a,b,x,y);
printf("%d\n",(x+b)%b);
return 0;
}
对于负数取模的处理方法:(求正整数解)
对于
对于
逆元
还没讲,留个位置。
整除分块
求一个式子:
暴力求法
long long dib(int n) {
long long res=0;
for(int i=1; i<=n; i=i+1) {
res=(res+n/i);
}
return res;
}
但在很多时候,
这个时候,就要使用更快的算法——整除分块。
例题:
UVA11526 H(n)
经过观察,发现有许多
设一个数据块左端点的值为
又因为
所以
有了左端点和右端点,每个块有多少个数也就很好求了。
模板:
long long dib(int x)
{
long long ans=0,j=1;
if(x==0)return 0;
for(long long i=1;i<=x;i=j+1)
{
j=x/(x/i);
ans+=(j-i+1)*(x/i);
}
return ans;
}
时间复杂度
后记
还没讲完, 讲完再记。