定理1算术基本定理:任何大于1的正整数都可以唯一分解为有限个质数的乘积
N=p1c1p2c2…pmcm
质数
试除法判断质数
时间复杂度 O( n \sqrt{n} n)
证明:为什么可以只判断到根号n?如果a小于等于 n \sqrt{n} n,同时a是n的因子,那么对应的n/a是大于等于 n \sqrt{n} n的,所以只需要判断小于的那部分即可
代码
bool isprime(int x)
{
if (x < 2) return false;
for (int i = 2; i <= x / i; i ++ )
if (x % i == 0)
return 0;
return 1;
}
分解质因数
时间复杂度O( n \sqrt{n} n)
证明:一个数n最多有一个大于 n \sqrt{n} n的因子,如果大于一个,那么这两个数相乘大于n,矛盾
代码:
void div(int x)
{
for(int i=2;i<=x/i;i++)
{
if(x%i==0)
{
int cnt=0;
while(x%i==0)
x/=i,cnt++;
cout<<i<<" "<<cnt<<endl;
}
}
if(x>1) cout<<x<<" "<<1<<endl;
}
筛质数
埃式筛法
思想:对于x,他的倍数都是合数,只需要将素数的倍数都标记为合数就可以
时间复杂度O(NloglogN)
分析:首先对于数x,他的倍数有 N x \frac{N}{x} xN个,x的范围是从2到N,所以是 ∑ i = 2 N \sum_{i=2}^N ∑i=2N N i \frac{N}{i} iN,根据调和级数,此时时间复杂度为NloglogN,1到N中大约有N/lnN个素数,时间复杂度为NloglogN
代码:
void get_primes(int n)
{
int cnt=0;
for(int i=2;i<=n;i++)
{
if(!st[i])
{
cnt++;
for(int j=2;j*i<=n;j++) st[j*i]=true;
}
}
cout<<cnt<<endl;
}
线性筛,虽然埃氏筛法已经接近线性,但是当n在1e7,线性筛运行时间大概是埃氏筛的一半
思想:仅仅使用某个数的最小质因子来标记
时间复杂度O(N)
代码:
void get_primes(int n)
{
int cnt=0;
for(int i=2;i<=n;i++)
{
if(!st[i])
{
prim[cnt++]=i;
}
for(int j=0;prim[j]*i<=n&&j<cnt;j++)
{
st[prim[j]*i]=true;
if(i%prim[j]==0) break;
}
}
cout<<cnt<<endl;
}
约数
约数个数
由算数基本定理可以得知n的一个约数可以写做p1ip2j…pmk,0<=i<=c1,0<=j<=c2…0<=k<=cm
结论:所以个数为(1+c1)(1+c2)…(1+cm)
代码:
cin>>t;
map<int,int> cnt;
ll ans=1;
while(t--)
{
cin>>n;
for(int i=2;i<=n/i;i++)
{
if(n%i==0)
{
while(n%i==0)
cnt[i]++,n/=i;
}
}
if(n>1) cnt[n]++;
}
for(auto x:cnt)
{
ans*=(x.second+1);
ans%=mod;
}
cout<<ans<<endl;
约数之和
结论:ans=(p10+p11+…p1c1)(p20+p21+…p2c2)…(pm0+pk1+…pkck)
证明:将该式展开可以得到所有的约数
代码:
cin>>t;
unordered_map<int,int> hash;
while(t--)
{
cin>>n;
for(int i=2;i<=n/i;i++)
{
if(n%i==0)
{
int s=0;
while(n%i==0)
{
s++;
n/=i;
}
hash[i]+=s;
}
}
if(n>1)hash[n]+=1;
}
ll ans=1;
for(auto x: hash)
{
ll res=1;
ll add=x.x;
for(int i=1;i<=x.y;i++)
{
res+=add;
res%=mod;
add*=x.x;
add%=mod;
}
ans=ans*res%mod;
}
cout<<ans<<endl;
欧拉函数
定义:1到N中与N互质的个数称为N的欧拉函数,记作 ϕ \phi ϕ(N)
结论 ϕ \phi ϕ(N)=N( p 1 − 1 p 1 \frac{p1-1}{p1} p1p1−1)( p 2 − 1 p 2 \frac{p2-1}{p2} p2p2−1)…( p m − 1 p m \frac{pm-1}{pm} pmpm−1)
证明:根据容斥原理,首先应该减去p1到pm的倍数,发现pipj的倍数减去了两次,那么应该在加上一个,然后可以发现pipj*pk的倍数分别在减去了三次加上了三次,所以应该在减去一次,得到规律,减去奇数项相乘,加上偶数项相乘,得到的就是结论中的公式
代码:
cin>>x;
ll res=x;
for(int i=2;i<=x/i;i++)
{
if(x%i==0)
{
res=res*(i-1)/i;
while(x%i==0)
x/=i;
}
}
if(x>1)res=res*(x-1)/x;
cout<<res<<endl;
定理2欧拉定理:如果gcd(a,m)等于1,那么a^ ϕ \phi ϕ(m) ≡ \equiv ≡ 1 ( m o d m ) \pmod{m} (modm)
逆元:当涉及到除法取模时经常需要用到逆元,ab%p=ab-1%p ,b*b-1 ≡ \equiv ≡ 1 ( m o d m ) \pmod{m} (modm)
证明当m是质数时,那么 ϕ \phi ϕ(m)=m-1,所以b(m-1) ≡ \equiv ≡ 1 ( m o d m ) \pmod{m} (modm) ,也就是b * b(m-2) ≡ \equiv ≡ 1 ( m o d m ) \pmod{m} (modm) ,所以b(m-2)就是b模m时的乘法逆元
扩展欧几里得
给定 n 对正整数 ai,bi,对于每对数,求出一组 xi,yi,使其满足 ai×xi+bi×yi=gcd(ai,bi)。
因为ai=kgcd,bi=kgcd,所以一定有解
运算过程,当a或者b其中有一个为0时,gcd为a与b中非0的那个,x与y分别就是0和1,由于gcd(a,b)=gcd(b,a%b)
假设存在一组x1和y1,使得b*x1+(a%b)*y1=gcd(b,a%b)=gcd(a,b)
bx1+(a-b* ⌊ \lfloor ⌊ a b \frac{a}{b} ba ⌋ \rfloor ⌋)y1=gcd(a,b)
通过递归可以求得x和y
代码
int exgcd(int a,int b,int&x,int&y)
{
if(!b)
{
x=1,y=0;
return a;
}
int d=exgcd(b,a%b,y,x);
y-=a/b*x;
return d;
}