数学知识-约数

约数

1.试除法求约数

1.1 算法描述

试除法求约数其实和试除法求质数是一样的,对于给定的正整数n,要求它的约数,我们只需要从1遍历到n,如果n%i==0,那么就是n的约数。

但同样的,我们可以做出优化,因为约数是成对出现的。因而,我们同样可以把i遍历的范围从1→n变为从i→sqrt(n),另外一个约数我们可以求出,整除这个约数得到的结果,那就是另外一个更大的约数。

1.2 代码实现

我们会选择用vector容器来存储已经求出的约数。在发现n%i==0时,将i插入容器:vector.push_back(i),再将另一个约数插入容器,需要注意的是,存在一种情况为:4=2*2,16=4 * 4,即约数的平方等于当前给定的数,因此我们要加入一个判定:if(x/i!=i),再执行vector.push_back(x/i)。并且在整个遍历完之后进行排序sort(res.begin(),res.end())

注意遍历容器的写法:for(auto t:res) cout<<t<<" ";

函数也可以返回容易,注意写法:vector<int> get_divisors(int x){……}

具体代码:

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
int n;
vector<int> get_divisors(int x){
    vector<int> res;
    for(int i=1;i<=x/i;i++){//注意条件是i<=x/i
        if(x%i==0){
            res.push_back(i);
            if(x/i!=i){
                res.push_back(x/i);//防止是平方,别加入重复的数字进去了
            }
        }
    }
    sort(res.begin(),res.end());//当然要排序啊
    return res;
}
int main(){
    cin>>n;
    vector<int> res;
    while(n--){
        int a;
        cin>>a;
        res=get_divisors(a);
        for(auto t:res) cout<<t<<" ";//学习一下这种遍历方式
        cout<<endl;
    }
}

1.3时间复杂度

时间复杂度为O(sqrt(n))。

2.试除法求约数个数

2.1 算法描述

首先,在先前的质数学习中我们知道,任何数是可以写成若干个质因子幂之积的形式的:

在这里插入图片描述
在这里插入图片描述
事实上,β1从0取到α1,β2从0取到α2……,任何一种取值都是N的约数。举个例子来说,若i≥2时,βi=αi,β1=α1-1,它就是N的约数,且倍数是p1.

所以说,我们将给定的N写成质因子幂之积的形式后,对于底数pi的质数,可以从0取到αi,共有αi+1中取法,因而,约数的个数为:

Num=(α1+1)(α2+1)+……+(αn+1)

所以我们只需要利用前面求质因子的方法, 把N拆分成质因子幂之积的形式即可,之后我们将所有的指数+1相乘,就可以得到答案。

那现在这个给定的数以ai的乘积给出,也就是说我要把每个ai都求出它的质因子幂之积,然后再相乘。但我会存在ai,aj有相同的质因子,这时候我就要把他们的指数加起来。首先你不能直接把ai都乘起来,因为这样会爆数的,显然也不可以用数组,ai一个都是2e9的数量级,数组空间开不到那么大的。而vector也不可以,当你求出一个它的底数,你如何能知道它是否存在,再把他们相加?你不能说我不加进去,我就占一个空间存储,这样是不行的,因为我们每个指数是要+1相乘的,如果占用一个下标,那α+1不是变成了(α1+1)(α2+1) (α1+α2=α)?这样结果就不对了。 因此我求出一个底数及其对应的指数,如果前面出现了这个底数,我就要加上这个指数,因而我们要用到unordered_map用底数当作map的索引

我们首先来回忆如何求质因子和它的指数:

for(int i=1;i<=n/i;i++){
	if(n%i==0){
		int s=0;
		while(n%i==0){
			n=n/i;
			s++;
		}
	}
}

那么在这里呢,我们不会再用s来记录它的指数,每当发现n%i=0时,就说明我的指数要+1,此时我的底数为i,那么通过索引i让对应的值加1,即primes[i]++;

2.2 代码实现

在实现该算法代码时,有以下几点需要注意:

1.Num=(α1+1)(α2+1)+……+(αn+1)这个数字是很大很大的,特别是你每一个ai可以达到2e9的数量级,因此不可以将num定义成int型,最好定义成long long 类型。

2.题目要求将所得的答案对 1e9+7 取模,事实上,Num由于太大了,可能连long long类型还是会爆掉,所以我们必须每拿到一个α,就对1e9取模:res=res*(t.second+1)%N,为什么可以这样书写?

假设现在primes里面存放了a,b。那么按照上面的写法,res=((a%mod*b)%mod 是否会等于(a*b)%mod ?

假设a=i* mod+p,b=j* mod+q,那么a b=i j+(iq+ jp)*mod+pq,那么(a*b)%mod=pq%mod;((a%mod*b)%mod=(pb)%b=(jpmod+qp)%mod=qp%mod=(a*b)%mod

总之,所有项乘积对mod求余会等同于按照每一项求余后再和下一项相乘求余的策略

具体代码:

#include<iostream>
#include<unordered_map>
using namespace std;
int n;
long long  res=1;
const int N=1e9+7;
int main(){
    unordered_map<int,int> primes;//存放底数和他对应的指数
    //其实你定义一个二元组本来也是可以的,但是你不可以重复
    cin>>n;
    while(n--){
        int a;
        scanf("%d",&a);
        for(int i=2;i<=a/i;i++){/*如果不写i<=a/i,其实你已经把所有质因子除完了,注意质因子会小于sqrt(a)
        最多只会有一个大于sqrt(a)*/
            if(a%i==0){
                while(a%i==0){
                    a=a/i;
                    primes[i]++;//说明底数是i,那么它对应的指数+1,map是有下标的
                }
            }
        }
        if(a>1) primes[a]++;
    }
    for(auto t:primes) res=res*(t.second+1)%N;//注意为什么要取模,以及正确性
    cout<<res;
}

3.求约数之和

事实上,所有的约数之和会等于:
在这里插入图片描述
应该是不用多加证明的…

我们已经用primes把所有的底数和指数存下来了,因此只需要在约数之和上稍作修改:对于取到t,那t.first就是p,t.second就是α,那通过下面的代码就可以把每一个乘积项求出来:

long long tmp=0;
        for(int i=0;i<=t.second;i++){
            tmp+=pow(t.first,i);
        }

那再仿照上面的,前面的结果res再和这一项相乘再取模:res=res*(t.second+1)%N

但这样做是不可行的,因为pow(t.first,i)太大了,tmp会爆掉。因此我们需要把(t.second+1)%N在过程中算出来:

由于
在这里插入图片描述
所以这也是递归的过程,就是前面的结果*p加上1,再对mod取余,就算集体往后挪一位,p1的0次幂由1补齐,即:
在这里插入图片描述

long long tmp=1;
        while(b--){
            tmp=(1+tmp*t.first)%N;
        }

具体代码:

#include<iostream>
#include<unordered_map>
#include<math.h>
using namespace std;
int n;
long long  res=1;
const int N=1e9+7;
int main(){
    unordered_map<int,int> primes;//存放底数和他对应的指数
    //其实你定义一个二元组本来也是可以的,但是你不可以重复
    cin>>n;
    while(n--){
        int a;
        scanf("%d",&a);
        for(int i=2;i<=a/i;i++){/*如果不写i<=a/i,其实你已经把所有质因子除完了,注意质因子会小于sqrt(a)
        最多只会有一个大于sqrt(a)*/
            if(a%i==0){
                while(a%i==0){
                    a=a/i;
                    primes[i]++;//说明底数是i,那么它对应的指数+1,map是有下标的
                }
            }
        }
        if(a>1) primes[a]++;
    }
    for(auto t:primes){
        int b=t.second;
        long long tmp=1;
        while(b--){
            tmp=(1+tmp*t.first)%N;
        }
        res=res*tmp%N;//注意为什么要取模,以及正确性
    }
    cout<<res;
}

事实上,其实就是求p的幂之和的递归式就是t=t*p+1,只不过我们为了防止数字太大,我们每次求完之后就取模一次,对结果是没有影响的。

4.求最大公约数

4.1算法描述

首先给出一个定理:

若 d|a,d|b(d整除a且d整除b),那么d|(ax+by)。

这样,对于给定的两个数a,b,就有(a,b)和(a,a%b)有相同的最大公约数。假设a=bc+a%b,即要证明(a,b)和(b,a-bc)有最大公约数。

那我们只需要证二者有相同的约数即可(因为有相同的b):假设x是(a,b)的约数,那么x|a,x|b,很显然a-bc是a,b的线性组合,即(a,b)的约数都是(b,a-bc)的约数;现假设x是(b,a-bc)的约数,即x|b,x|(a-bc),而a=bc+(a-bc),也是关于b,a-bc的线性组合,因而(b,a-bc)的约数都是(a,b)的约数。

4.2代码实现

int gcb(int a,int b){
	if(b==0) return a;
	else gcb(b,a%b);
}

需要注意的是,为什么不可以写成gcb(a%b,b)的递归形式?

因为我始终保证a会大于b,a%b<b,所以一定是传进来的b会为0.如果写成gcb(a%b,b)的形式,传进来的a一定会小于b,那a%b=a,那就是gcb(a,b)无限递归了。因此我写成这样的形式,就是保证a%b不是一个小的数去整除一个大的数了。要把整除的放前面也可以,但是你要知道这样第一个参数一定是更小的,更大的整除更小的,就要写成:

int gcb(int a,int b){
	if(a==0) return b;
	else gcb(b%a,a);
}

其实就是a与b的互换…

具体代码:

#include<iostream>
using namespace std;
int n;
int gcb(int a,int b){
    if(b==0){
        return a;
    }
    else{
        gcb(b,a%b);
    }
}
int main(){
    cin>>n;
    while(n--){
        int a,b;
        cin>>a>>b;
        int res=gcb(a,b);
        cout<<res<<endl;
    }
}

感谢AcWing平台

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值