数论基础入门

本文深入探讨了整数的整除性质、质数与合数的概念及其分布规律,重点介绍了素数定理、质因数分解以及最大公因数和最小公倍数的计算方法。此外,详细阐述了欧几里得算法、筛法等在素数判定中的应用,并通过实例展示了如何高效地解决实际编程问题,如求最大公因数、完全平方数识别以及寻找连续素数序列。
摘要由CSDN通过智能技术生成

一些知识

整除

自然数a可以被自然数b整除或者说b是a的约数表示的是:
a % b = 0 a\%b=0 a%b=0,即a是b的倍数,b是a的约数(不要弄反了),记作b|a。
(0是自然数也是整数)

质数(素数)和合数

指在大于1的自然数中,除了1和它本身不再有其他因数的自然数(如:2,3,5);一个大于1的整数,如果除了1和它本身以外,还有其他的约数,这样的数就叫作合数。

素数的分布

目前素数的分布还不能确定,但是能够给出一个近似分布, π ( n ) \pi(n) π(n)为不超过n的质数的个数, π ( n ) ∼ n l n n \pi(n) \sim \frac{n}{lnn} π(n)lnnn
证明灰常的复杂,感兴趣的可以看这里

质因数

对于给定的自然数n,x是其质因数,当且x是n的因数且x是质数。

最大公因数

记作gcd()。

最小公倍数

记作lcm()。

互质

如果两个或两个以上的整数的最大公约数为1,则他们互质,记作 a ⊥ b a \bot b ab

唯一分解定理

任何一个大于1的数都可以被分解为有限个质数乘积的形式,并且分解方式是唯一的,
质数是一个整数最基本的特征,很多问题都从质数的角度考虑
如:300=22355 12=223
一个结论:
a = q 1 n 1 q 2 n 2 a=q_1^{n_1} q_2^{n_2} a=q1n1q2n2,且b|a,则 b = q 1 b 1 q 2 b 2 b=q_1^{b_1} q_2^{b_2} b=q1b1q2b2,且 b i 大 于 等 于 0 , 小 于 与 等 于 n i b_i大于等于0,小于与等于n_i bi0ni
对于下图中的约束,对应约束个数:
(来源参考
在这里插入图片描述
约束个数中加一是考虑了还有1

完全平方数

若一个数能表示成某个整数的平方的形式,则称这个数为完全平方数。

例题

求解两个数最大公因数

如果暴力求解的话,即从一开始遍历,复杂度为 O ( m i n a , b ) O(min{a,b}) O(mina,b);而欧几里得算法(辗转相除法复杂度可化简为 O ( l o g n ) O(logn) O(logn)

定理

gcd(a,b)=gcd(b,r),其中r为a/b的余数
严格的逻辑证明这里省略,简单的说下思路:
假设gcd(a,b)=c, 那么假设 a = k 1 c , b = k 2 c a=k_1c,b=k_2c a=k1c,b=k2c, a / b = k … r a/b=k…r a/b=kr,则 r = a − b k = k 1 c − b k 2 c = ( k 1 − b k 2 ) c r=a-bk=k_1c-bk_2c=(k_1-bk_2)c r=abk=k1cbk2c=(k1bk2)c,即可以得到r也是c的倍数,还要说明c是r和b的最大公因数,
现在 b = k 2 c b=k_2c b=k2c, r = ( k 1 − b k 2 ) c r=(k_1-bk_2)c r=(k1bk2)c,需要证明 k 2 和 ( k 1 − b k 2 ) k_2和(k_1-bk_2) k2(k1bk2)互质,可以用反证法,如果不互质的话,会存在一个比c大的公因数,矛盾,所以不成立。

代码

int gcd(int a,int b)
{
	return b==0? a:gcd(b,a%b);
}
//b=0的时候,假如gcd(6,3),那么下一步递归得到gcd(3,0)即为3

补充性质

g c d ( k a , k b ) = k g c d ( a , b ) gcd(ka,kb)=k gcd(a,b) gcdka,kb=kgcd(a,b)
l c m ( a , b ) = a ∗ b g c d ( a , b ) lcm(a,b)=\frac{a*b}{gcd(a,b)} lcm(a,b)=gcd(a,b)ab

素数判定

枚举是否含有除了1和本身的因数,其实只用枚举 2 − n 2-\sqrt{n} 2n 就行,因为如果不是素数,那么必存在 n = a b n=ab n=ab,用反证法可以证明必有一个数小于 n \sqrt{n} n ;

代码

bool is_prime(int n)
{
	if(x<=1)return false;
	for(int i=2;i*i<=n;i++)
	{
		if(x%i!=0)return true;
	}
	return false;
}

多个素数判定

假如给你1-n,n个数,让你判断这n个数是不是素数,如果按照上述算法每个判断的话,复杂度为 O ( n n ) O(n\sqrt{n}) O(nn ),下边引入一种筛法,可以将复杂度降为 O ( n l o g n ) O(nlogn) O(nlogn).

筛法:
一个非素数,肯定可以被它的约数整数,那枚举约数把约束的倍数标记,未被标记的数就是质数

代码

void is_primes(int n)
{
	for(int i=2;i<=n;i++)
	{	
		for(int j=i;j<=n/i;j++)
		{	
			v[i*j]=1;
		}
	}
	
}
//复杂度计算:n(1+1/2+1/3+……)调和级数

代码优化-埃氏筛

刚刚那个会有很多重复标记,比如24,在i=2(212)的时候标记一次,在i=4(46)的时候标记一次
这次优化减少了部分重复计算
从质数的倍数都为合数的角度(刚刚是1-n所有数的倍数,由唯一分解定理可知,合数一定可以分解为素数的乘积),每次只标记素数的倍数,因为合数的话一定可以写为素数相乘的形式,在素数时被标记过一次,如果合数再标记一次就会重复。

void is_primes(int n)
{
	for(int i=2;i<=n;i++)
	{	
	//是不是素数,初始化均为素数0
		if(v[i]) continue;
		//是素数的话就开始标记
		for(int j=i;j<=n/i;j++)
		{	
			v[i*j]=1;
		}
	}
	
}
//复杂度:O(nloglogn)

欧拉筛(继续优化)

上边的方法还是会有重复,比如12,在i=4(3)、6(2)的时候都会被标记一次,所以可以每次用一个数最小的质因子去筛掉合数(用素数去筛合数)且只用最小质因子去一次就行(比如12,只用素数2筛一次也就是只在62的时候再筛(更大的倍数i乘以更小的素数计算出12),所以要达到内层循环在乘完42就break的效果)

void is_primes(int n)
{
	for(int i=2;i<=n;i++)
	{	
		if(!v[i]) primes[++cnt]=i; //,初始化为0代表全是素数,i是素数
		for(int j=1;j<=cnt&&i*primes[j]<=n;j++)
		{	
			v[i*primes[j]]=1; //所有质数的i倍,从最小倍开始,防止重复
			if(i%primes[j]==0)break;
			//如果i是prime的倍数的话
			//每个合数只被它的最小质因子筛一次
			//比如:12当i=12;prime=2(指的是prime是最小质因子而不是外层循环,外层循环代表的是倍数)的时候24才会被筛掉,所以i=8时对应prime=3会break掉(也就是i=8除以2为整数的时候)
			// 整体的来讲,i是prime[j]的倍数时,i = kprime[j],若不break继续算 i乘以下一个,则i * prime[j+1] = prime[j] * k prime[j+1],这里prime[j]是最小的质因子,也就是算了两次,当i = k * prime[j+1]时会多重复了一次,所以才跳出循环。
		}
	}
	
}
//复杂度:O(n)

请添加图片描述

牛牛与LCM

(C++库中自带求最大公因子函数,可以直接拿来用)
在这里插入图片描述

#include<bits/stdc++.h>
using namespace std;
//若干个数,不是两个数
int lcm(int a,int b)
{
    return a*b/__gcd(a,b);
}
int main()
{
    int n;
    cin>>n;
    int *a=(int *)malloc(sizeof(int)*n);
    for(int i=0;i<n;i++)
    {
        cin>>a[i];
    }
    int x;
    cin>>x;
//这里要开long long
    long long ans=1;
    for(int i=0;i<n;i++)
    {
        //x是a[i]的倍数
        //不仅要是倍数还要是最小公倍数
        if(x%a[i]==0)
        {
            ans=lcm(ans,a[i]);
        }
    }
    if(ans==x)
    {
        cout<<"Possible";
    }
    else
        cout<<"Impossible";
    return 0;
}

Sum of Consecutive Prime Numbers

在这里插入图片描述
这个题涉及两个知识点,求1~n的素数有哪些,上边已经讲解过欧拉可以达到线性复杂度;另一个点就是在一串从小到大的数组中,找到和为x的一段,起初看到的时候,基于现有的知识,首先想到的是前缀和,也就是说:先求出前缀和序列,然后借助两个指针表示所选取的序列,指针所指两个前缀和序列对应数做减法得到指针之间这段序列的和,通过移动指针来得到最终结果(指针的初始位置均指向第一个数,若所指段和小于x,则右指针右移,若大于则左指针右移)。
但是看完题解,学到了基于滑动窗口的方法做这个题,整体思路一样,借助两个指针,通过移动来找到最终结果,初始sum值即为第一个数,当右指针右移时sum加当左指针右移时sum减,sum与x比较,同样可以得到最终结果,并且省去了一个做前缀和的过程。

//题目给出了n的上限,且如果每次只按照当前n值找素数的话,会比较麻烦,所以直接一次性计算到上限
#include<bits/stdc++.h>
using namespace std;

int main()
{
    //复习下,先不要看之前的笔记,需要维护一个素数数组和结果数组
    int prime[int(4e7)+1];
    bool is[int(4e7)+1];
    //开两个int,空间会超,改成bool
    int cnt=0;
    memset(prime,0,sizeof(int)*(int(4e7)+1));
    memset(is,0,sizeof(bool)*(int(4e7)+1));
    for(int i=2;i<=int(4e7);i++)
    {
        if(!is[i])prime[++cnt]=i;
        for(int j=1;j<=cnt&&i*prime[j]<=int(4e7);j++)
        {
            is[i*prime[j]]=1;
            if(i%prime[j]==0)break;
        }
    }

    int T;
    cin>>T;
    int flag=1;
    while(flag<=T)
    {
        flag++;
        int n;
        cin>>n;
        int l=1;
        int r=1;
        int ans=0;
        int sum=prime[1];
        for(;l>=1 && r<=cnt&&l<=r;)
        {
            if(sum>=n)
            {
                if(sum==n)ans++;
                sum-=prime[l];
                l++;     
                
            }
            else if(sum<n)
            {
                //这里顺序不要反了啊
                r++;
                sum+=prime[r];
                
            }
        }
        cout<<ans<<endl;        
    }
    
    return 0;
}

Prime Distance(区间筛)

在这里插入图片描述
在素数判定中有提到,只用循环至 n \sqrt n n ,那么同理,一个数n的最小质因子一定小于等于 n \sqrt n n (同样反证法),也就是说,我们仅需要知道2~ n \sqrt n n 的素数,就可以筛选1~n

这个题一定要记得long long!!


//这个题目和上个题目不一样之处在于,开不出来2的31次方的数组,并且筛到2的31次方的话即使是线性复杂度也会超时
//所以要做区间筛
#include <bits/stdc++.h>
using namespace std;
const int N=1e6+10;
//只筛选出该范围内的素数就行
const int range=46341+10;
int prime[range];
bool is[range];
int p[N];
int max(long long a,long long b)
{
    return a>b?a:b;
}
int main() 
{
    int cnt=0;
    memset(is,0,sizeof(int)*(range));
    for(int i=2;i<=range-10;i++)
    {
        if(!is[i])prime[++cnt]=i;
        for(int j=1;j<=cnt&&i*prime[j]<=range-10;j++)
        {
            is[i*prime[j]]=1;
            if(i%prime[j]==0)break;
        }
    }
    int T;
    cin>>T;
    long long r;
    long long l;
    while(T--)
    {
        memset(p,0,sizeof(int)*(N));
        cin>>l>>r;
        //筛区间的时候用埃氏筛,欧拉筛会超时
        //因为直接按照倍数标记就行了,欧拉的话还要循环至质因数最小的那个倍数
        /*
        for(int i=max(2,l/prime[cnt]);i<=r/2+1;i++)
        {
            for(int j=1;j<=cnt&&i*prime[j]>=l&&i*prime[j]<=r;j++)
            {
                p[i*prime[j]-l]=1;
                if(i%prime[j]==0)break;
            }
        }*/
        for (long long i = 1; i <= cnt && prime[i] <= r; i++) {
            long long  w = (l + prime[i] - 1) / prime[i];//第一个数是i的多少倍
            for (long long j = max(2, w); prime[i] * j <= r; j++)
                p[prime[i] * j - l] = 1;
        }
        vector<long long int > pri;
        for (long long  i = max(2, l); i <= r; i++) { 
            if (!p[i - l]) pri.push_back(i);
            p[i - l] = 0;
        }
        /*
        for(int i=0;i<N;i++)
        {
            if(i+l>r)break;
            if(p[i]==0)pri.push_back(i);
        }*/
        if(pri.size()<=1){
            cout<<"There are no adjacent primes."<<endl;
            continue;
        }
        else{
            pair<long long,long long> min={0x3f3f3f3f3f,-1},max={-1,-1};
            for(long long i=0;i<pri.size()-1;i++)
            {
                if(pri[i+1]-pri[i]>max.first){
                    max.first=pri[i+1]-pri[i];
                    max.second=i;
                    
                }
                if(pri[i+1]-pri[i]<min.first){
                    min.first=pri[i+1]-pri[i];
                    min.second=i;                  
                }
            }
            cout<<pri[min.second]<<','<<pri[min.second+1]<<" are closest, "
                <<pri[max.second]<<','<<pri[max.second+1]<<" are most distant."
                <<endl;
            continue;
            
        }
        
    }
    return 0;
}

实现唯一分解定理

就是从最小的素数开始一个一个除

for(int i=1;i<=cnt&&i<sqrt(n);i++)
{
	while(n%prime[i]==0)
	{
		a[c++]=prime[i];
		n=n%prime[i];
		
	}
}

X-factor Chains

在这里插入图片描述
题中要求序列尽可能地长,也就是所有 a i + 1 = t ∗ a i a_{i+1}=t*a_i ai+1=tai中的t都要尽量的小,换个思路,质数是不能再分的,也就是说如果4*a可以写作 2 ∗ 2 ∗ a 2*2*a 22a从而达到t最小的效果,所以这道题就是实现唯一分解定理,然后对素数计算全排列

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值