约数
1.约数的定义
约数,又称因数。 整数 a a a 除以整数 b b b ( b ≠ 0 b \neq 0 b=0) 除得的商正好是整数而没有余数,即 b ∣ a b|a b∣a。我们就说 a a a 能被 b b b 整除,或 b b b 能整除 a a a 。 a a a 称为 b b b 的倍数, b b b 称为 a a a 的约数。
2.习题
1.求一个数所有的约数
我们可以从枚举从 1 1 1 到 x \sqrt{x} x,x能被i整除的话我们就可以获取两个约数。(需要注意当 i = x / i i = x/i i=x/i的特殊情况,这种情况,我们只需要记录一个约数)
时间复杂度: O ( n ) O(\sqrt{n}) O(n)
C++代码:
vector<int> get(int x){
vector<int> res;
for(int i = 1;i <= x / i;i++){
if(x % i==0){
res.push_back(i);
//如果 i != x/i,才添加第二个约数
if(i != x/i) res.push_back(x/i);
}
}
sort(res.begin(),res.end());
return res;
}
java代码:
public static List<Integer> get(int x){
List<Integer> list = new ArrayList<>();
for(int i = 1;i <= x/i;i++){
if(x % i == 0){
list.add(i);
if(i != x/i) list.add(x/i);
}
}
Collections.sort(list);
return list;
}
2.求约数的个数
由唯一分解定理:任一大于 1 1 1 的自然数,要么本身是质数,要么可以分解为几个质数之积,且这种分解是唯一的。例如: 20 = 2 × 2 × 5 20 = 2 \times 2 \times 5 20=2×2×5, 8 = 2 × 2 × 2 8 = 2 \times 2 \times 2 8=2×2×2。
一个数 x x x 必然可以以这样的形式表示出来 x = p 1 a 1 × p 2 a 2 × … p n a n x = p_1^{a_1} \times p_2^{a_2} \times…p_n^{a_n} x=p1a1×p2a2×…pnan( p p p 是质因子, a a a是该质因子的个数)。例如: 36 = 2 2 × 3 2 36 = 2^2 \times 3^2 36=22×32
所以 x x x 的约数个数就可以得出为: ( a 1 + 1 ) × ( a 2 + 1 ) × . . . ( a n + 1 ) (a_1 + 1) \times (a_2 + 1) \times ...(a_n + 1) (a1+1)×(a2+1)×...(an+1)。例如:若 c = a 2 × b 2 c = a^2 \times b^2 c=a2×b2,那么 c c c 的因子有( 1 , b , b 2 , a , a b , a b 2 , a 2 , a 2 b , a 2 b 2 1,b,b^2,a,ab,ab^2,a^2,a^2b,a^2b^2 1,b,b2,a,ab,ab2,a2,a2b,a2b2)共 9 9 9 个。
时间复杂度: O ( n × a ) O(n \times \sqrt{a}) O(n×a)
C++代码:
void divide(int x){
//哈希表记录质因子 以及 它的个数
unordered_map<int,int> cnt;
int old = x;
//分解质因数
for(int i = 2;i <= x/i;i++){
if(x % i==0){
while(x % i == 0){
cnt[i]++;
x /= i;
}
}
}
if(x > 1) cnt[x]++;
//计算约数个数
int ans = 1;
//枚举哈希表,k代表枚举到的质因子,v代表质因子k的个数
for(auto &[k,v]:cnt){
ans *= (v + 1);
}
printf("%d 的约数个数为 %d\n",old,ans);
}
java代码:
public static void divide(int x){
Map<Integer, Integer> map = new HashMap<>();
int old = x;
for(int i = 2;i <= x/i;i++){
if(x % i == 0){
while(x % i == 0){
map.put(i,map.getOrDefault(i,0)+1);
x /= i;
}
}
}
if(x > 1) map.put(x,1);
int ans = 1;
for(Integer key:map.keySet()){
int v = map.get(key);
ans *= (v + 1);
}
System.out.println(old +" 的约数个数为: "+ans);
}
3.求约数之和
约数之和的公式为: s u m = ( p 1 0 + p 1 1 + . . . + p 1 a 1 ) × ( p 2 0 + p 2 1 + . . . + p 2 a 2 ) × . . . ( p n 0 + p n 1 + . . . + p n a n ) sum = (p_1^0 + p_1^1 + ...+p_1^{a_1}) \times (p_2^0 + p_2^1 + ...+p_2^{a_2}) \times ...(p_n^0 + p_n^1 + ... + p_n^{a_n}) sum=(p10+p11+...+p1a1)×(p20+p21+...+p2a2)×...(pn0+pn1+...+pnan)
例如: c = a 2 × b 2 c = a^2 \times b^2 c=a2×b2,那么 c c c 的约数有: ( 1 , b , b 2 , a , a b , a b 2 , a 2 , a 2 b , a 2 b 2 ) (1,b,b^2,a,ab,ab^2,a^2,a^2b,a^2b^2) (1,b,b2,a,ab,ab2,a2,a2b,a2b2) 共 9 9 9 个,化简可得 s u m = ( 1 + a + a 2 ) × ( 1 + b + b 2 ) sum = (1+a+a^2)\times (1+b+b^2) sum=(1+a+a2)×(1+b+b2)
时间复杂度: O ( n × a ) O(n \times \sqrt{a}) O(n×a)
C++代码:
//用 long long 防止溢出
typedef long long LL;
//数据可能非常大,所以需要进行取模处理
const int MOD = 1e9+7;
void divide(int x){
int old = x;
//记录质因子,和它的个数
unordered_map<int,int> cnt;
for(int i = 2;i <= x/i;i++){
if(x % i==0){
while(x % i == 0){
cnt[i]++;
x /= i;
}
}
}
if(x > 1) cnt[x]++;
LL res = 1;
for(auto &[k,v]:cnt){
LL t = 1;
/*
v = 1时,t = k + 1
v = 2时,t = k^2 + k + 1;
v = 3时,t = k^3 + k^2 + k + 1
*/
//这里取模也是为了防止溢出
while(v--) t = (t * k + 1)%MOD;
res = res * t % MOD;
}
cout<<old<<"的约数之和为: "<<res<<"\n";
}
java代码:
private static final int MOD = (int)Math.pow(10,9);
public static void divide(int x){
Map<Integer, Integer> map = new HashMap<>();
int old = x;
for(int i = 2;i <= x/i;i++){
if(x % i == 0){
while(x % i == 0){
map.put(i,map.getOrDefault(i,0)+1);
x /= i;
}
}
}
if(x > 1) map.put(x,1);
long ans = 1;
for(Integer key:map.keySet()){
int v = map.get(key);
long t = 1;
while(v>0){
t = (t * key + 1)% MOD;
v--;
}
ans *= t;
}
System.out.println(old +" 的约数之和为: "+ans);
}
3.例题
1.试除法求约数
题目链接
题目描述
给定 n n n 个正整数 a i a_i ai,对于每个整数 a i a_i ai,请你按照从小到大的顺序输出它的所有约数。
输入格式
第一行包含整数 n n n。
接下来 n n n 行,每行包含一个整数 a i a_i ai。
输出格式
输出共 n n n 行,其中第 i i i 行输出第 i i i 个整数 a i a_i ai 的所有约数。
数据范围
- 1 ≤ n ≤ 100 1≤n≤100 1≤n≤100
- 2 ≤ a i ≤ 2 × 1 0 9 2≤a_i≤2×10^9 2≤ai≤2×109
输入样例:
2
6
8
输出样例:
1 2 3 6
1 2 4 8
时间复杂度: O ( n × n ) O(n \times \sqrt{n}) O(n×n)
C++代码:
#include<bits/stdc++.h>
using namespace std;
int n;
vector<int> get(int x){
vector<int> res;
for(int i = 1;i <= x / i;i++){
if(x % i==0){
res.push_back(i);
if(i != x/i) res.push_back(x/i);
}
}
return res;
}
int main(){
cin>>n;
while(n--){
int x;
cin>>x;
auto t = get(x);
sort(t.begin(),t.end());
for(auto &num:t) printf("%d ",num);
puts("");
}
return 0;
}
2.约数个数
题目链接
题目描述
给定 n n n 个正整数 a i a_i ai,请你输出这些数的乘积的约数个数,答案对 1 0 9 + 7 10^9+7 109+7 取模。
输入格式
第一行包含整数 n n n。
接下来 n n n 行,每行包含一个整数 a i a_i ai。
输出格式
输出一个整数,表示所给正整数的乘积的约数个数,答案需对 1 0 9 + 7 10^9+7 109+7 取模。
输入样例:
3
2
6
8
输出样例:
12
时间复杂度: O ( n × a ) O(n \times \sqrt{a}) O(n×a)
C++代码:
#include<bits/stdc++.h>
using namespace std;
const int MOD = 1e9+7;
typedef long long LL;
unordered_map<int,int> cnt;
int n;
void divide(int x){
for(int i = 2;i <= x/i;i++){
if(x % i==0){
while(x % i == 0){
cnt[i]++;
x /= i;
}
}
}
if(x > 1) cnt[x]++;
}
int main(){
cin>>n;
LL res = 1;
while(n--){
int x;
cin>>x;
divide(x);
}
for(auto &[k,v]:cnt) res = res * (v + 1) % MOD;
cout<<res<<"\n";
return 0;
}
3.约数之和
给定 n n n 个正整数 a i a_i ai,请你输出这些数的乘积的约数之和,答案对 1 0 9 + 7 10^9+7 109+7 取模。
输入格式
第一行包含整数 n n n。
接下来 n n n 行,每行包含一个整数 a i a_i ai。
输出格式
输出一个整数,表示所给正整数的乘积的约数之和,答案需对 1 0 9 + 7 10^9+7 109+7 取模。
输入样例:
3
2
6
8
输出样例:
252
时间复杂度: O ( n × a ) O(n \times \sqrt{a}) O(n×a)
C++代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int MOD = 1e9+7;
unordered_map<int,int> cnt;
int n;
void divide(int x){
for(int i = 2;i <= x/i;i++){
if(x % i==0){
while(x % i == 0){
cnt[i]++;
x /= i;
}
}
}
if(x > 1) cnt[x]++;
}
int main(){
cin>>n;
while(n--){
int x;
cin>>x;
divide(x);
}
LL res = 1;
for(auto &[k,v]:cnt){
LL t = 1;
while(v--) t = (t * k + 1)%MOD;
res = res * t % MOD;
}
cout<<res<<"\n";
return 0;
}