约数
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;
}
}