[Java学习]约数相关问题

本文深入解析了约数的定义、算术基本定理的推论,包括试除法求公约数的方法,以及约数个数和约数之和的计算技巧。通过实例演示,理解并掌握了如何运用这些理论解决实际问题,如找出正整数的约数集和计算其乘积的约数属性。
摘要由CSDN通过智能技术生成


前言

最近在学习数论的相关知识,特此整理一下。本篇主要介绍了约数的一些问题。


相关数学知识

约数定义

若整数 n n n 除以整数 d d d 的余数为0,即 d d d 能整除 n n n,则称 d d d n n n 的约数。


算术基本定理的推论

在算数基本定理中,若正整数 N N N 被唯一分解为 N = p 1 c 1 p 2 c 2 ⋯ p n c m N=p_1^{c_1}{p_2}^{c_2}\cdots {p_n}^{c_m} N=p1c1p2c2pncm,其中 c i c_i ci 都是正整数, p i p_i pi 都是质数,且满足 p 1 < p 2 < ⋯ < p m p_1<p_2<\cdots<p_m p1<p2<<pm,则 N N N 的正约数集合可以写作:
V = { p 1 b 1 p 2 b 2 ⋯ p m b m } V=\left\{p_1^{b_1}p_2^{b_2}\cdots p_m^{b_m}\right\} V={p1b1p2b2pmbm},其中 0 ≤ b i ≤ c i 0\leq b_i \leq c_i 0bici

N N N 的正约数个数为:
( c 1 + 1 ) ( c 2 + 1 ) ⋯ ( c m + 1 ) = ∏ i = 1 m ( c i + 1 ) (c_1+1)(c_2+1) \cdots(c_m+1)=\prod \limits_{i=1}^m(c_i+1) (c1+1)(c2+1)(cm+1)=i=1m(ci+1)

N N N 的所有正约数之和
( p 1 0 + p 1 1 + p 1 2 + ⋯ + p 1 c 1 ) ∗ ⋯ ∗ ( p m 0 + p m 1 + ⋯ + p m c m ) = ∏ i = 1 m ( ∑ j = 0 c i ( p i ) j ) (p_1^{0}+p_1^{1}+p_1^{2}+\cdots+p_1^{c_1})*\cdots*(p_m^{0}+p_m^{1}+\cdots+p_m^{c_m})=\prod\limits_{i=1}^m(\sum\limits_{j=0}^{c_i}(p_i)^{j}) (p10+p11+p12++p1c1)(pm0+pm1++pmcm)=i=1m(j=0ci(pi)j)

具体的证明方法后面会详细介绍。


试除法求公约数

试除法

  若 d ≥ N d\geq\sqrt{N} dN N N N 的约数,则 N / d ≤ N N/d\leq\sqrt{N} N/dN 也是 N N N 的约数。换言之,约数总是成对出现的(对于完全平方数, N \sqrt{N} N 会单独出现)

 因此只需扫描 d = 1 d=1 d=1 d = N d=\sqrt{N} d=N 的约数,尝试 d d d 能否整除 N N N,若能整除,则 N / d N/d N/d 也是 N N N 的约数。时间复杂度为 O ( N ) O(\sqrt{N}) O(N )


具体事例

给定 n n n
个正整数 a i a_i ai
对于每个整数 a i a_i ai
请你按照从小到大的顺序输出它的所有约数。

解决该问题的代码如下:

import java.io.*;
import java.util.*;

class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
        int n = Integer.parseInt(bufferedReader.readLine());
        for (int i = 0; i < n; i ++) {
            int a = Integer.parseInt(bufferedReader.readLine());
            var res = get_divisors(a);
            for (int x : res) {
                System.out.print(x + " ");
            }
            System.out.println();
        }
        bufferedReader.close();
    }
    
    public static List<Integer> get_divisors(int x) {
        List<Integer> res = new ArrayList<>();
        for (int i = 1; i <= x / i; i ++) {
            if (x % i == 0) {
                res.add(i);
                //判断x是否为完全平方数
                if (x / i != i) {
                    res.add(x / i);
                }
            }
        }
        Collections.sort(res);
        return res;
    }
}

这里利用一个List集合来存储一个数的约数。代码比较简单易懂。


约数的个数

约数个数推导

N = p 1 α 1 ∗ p 2 α 2 ∗ ⋯ ∗ p k α k N=p_1^{\alpha_1}*{p_2}^{\alpha_2}*\cdots* {p_k}^{\alpha_k} N=p1α1p2α2pkαk

因为任意一个约数 d d d 可以表示为:
d = p 1 β 1 ∗ p 2 β 2 ∗ ⋯ ∗ p k β k d=p_1^{\beta_1}*p_2^{\beta_2}*\cdots*p_k^{\beta_k} d=p1β1p2β2pkβk,其中 0 ≤ β i ≤ α i 0\leq\beta_i\leq\alpha_i 0βiαi

如果每一项的 β i \beta_i βi 不相同,那么约数 d d d 也就不相同(根据算数基本定理,每个数的因数分解是唯一的)。

所以 N N N 的约数就跟 β i \beta_i βi的取法是一一对应的
其中, β 1 \beta_1 β1 0 ∼ α 1 0\sim\alpha_1 0α1 种取法;
β 2 \beta_2 β2 0 ∼ α 2 0\sim\alpha_2 0α2 种取法;
⋯ ⋯ \cdots\cdots
β k \beta_k βk 0 ∼ α k 0\sim\alpha_k 0αk 种取法;

因此,约数的个数为
( α 1 + 1 ) ∗ ( α 2 + 1 ) ⋯ ( α k + 1 ) = ∏ i = 1 k ( α i + 1 ) (\alpha_1+1)*(\alpha_2+1) \cdots(\alpha_k+1)=\prod \limits_{i=1}^k(\alpha_i+1) (α1+1)(α2+1)(αk+1)=i=1k(αi+1)


具体事例

给定 n n n
个正整数 a i a_i ai
请你输出这些数的乘积的约数个数,答案对 1 0 9 + 7 10^9+7 109+7 取模。

实现代码如下:

import java.io.*;
import java.util.*;

class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
        int n = Integer.parseInt(bufferedReader.readLine());
        int mod = 1000000007;
        Map<Integer, Integer> map = new HashMap<>();
        for (int i = 0; i < n; i ++) {
            int a = Integer.parseInt(bufferedReader.readLine());
            for (int j = 2; j <= a / j; j ++) {
                while (a % j == 0) {
                    a /= j;
                    //作用:当 Map 集合中有这个 key 时,就使用这个 key 值;如果没有就使用默认值 defaultValue
                    map.put(j, map.getOrDefault(j, 0) + 1);
                }
            }
            if (a > 1) {
                map.put(a, map.getOrDefault(a, 0) + 1);
            }
        }
        
        long ans = 1;
        for (Map.Entry<Integer, Integer> entry: map.entrySet()) {
            ans = ans * (entry.getValue() + 1) % mod;
        }
        System.out.println(ans);
    }
}

这里面的Map集合存储的是每一个底数和底数对应的指数,而对于若干数乘积取约数可以通过分别对每一个乘数取约数进行求解。


约数之和

约数之和推导

约数之和推导相对简单,令 N = p 1 α 1 ∗ p 2 α 2 ∗ ⋯ ∗ p k α k N=p_1^{\alpha_1}*{p_2}^{\alpha_2}*\cdots* {p_k}^{\alpha_k} N=p1α1p2α2pkαk

可以计算出 p 1 α 1 p_1^{\alpha_1} p1α1 的约数之和为 ( p 1 0 + p 1 1 + ⋯ + p 1 α 1 ) (p_1^{0}+p_1^{1}+\cdots+p_1^{\alpha_1}) (p10+p11++p1α1).

同理, p 2 α 2 p_2^{\alpha_2} p2α2 的约数之和为 ( p 2 0 + p 2 1 + ⋯ + p 2 α 2 ) (p_2^{0}+p_2^{1}+\cdots+p_2^{\alpha_2}) (p20+p21++p2α2).

⋯ ⋯ \cdots\cdots

p k α k p_k^{\alpha_k} pkαk 的约数之和为 ( p k 0 + p k 1 + ⋯ + p k α k ) (p_k^{0}+p_k^{1}+\cdots+p_k^{\alpha_k}) (pk0+pk1++pkαk).

因此 N N N 的约数之和为:

( p 1 0 + p 1 1 + p 1 2 + ⋯ + p 1 α 1 ) ∗ ⋯ ∗ ( p k 0 + p k 1 + ⋯ + p k α k ) = ∏ i = 1 k ( ∑ j = 0 α i ( p i ) j ) (p_1^{0}+p_1^{1}+p_1^{2}+\cdots+p_1^{\alpha_1})*\cdots*(p_k^{0}+p_k^{1}+\cdots+p_k^{\alpha_k})=\prod\limits_{i=1}^k(\sum\limits_{j=0}^{\alpha_i}(p_i)^{j}) (p10+p11+p12++p1α1)(pk0+pk1++pkαk)=i=1k(j=0αi(pi)j)

再利用程序求解约数之和的式子时,可以利用下面的方法:

t = t ∗ p + 1 t=t*p+1 t=tp+1

开始时:
t = 1 t=1 t=1
t = p + 1 t=p+1 t=p+1
t = p 2 + p + 1 t=p^2+p+1 t=p2+p+1
⋯ ⋯ \cdots\cdots
t = p α + p α − 1 + ⋯ + p + 1 t=p^{\alpha}+p^{\alpha-1}+\cdots+p+1 t=pα+pα1++p+1

具体事例

给定 n n n
个正整数 a i a_i ai
请你输出这些数的乘积的约数之和,答案对 1 0 9 + 7 10^9+7 109+7 取模。

实现代码如下:

import java.io.*;
import java.util.*;

class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
        int n = Integer.parseInt(bufferedReader.readLine());
        Map<Integer, Integer> map = new HashMap<>();
        int mod = 1000000007;
        for (int i = 0; i < n; i ++) {
            int a = Integer.parseInt(bufferedReader.readLine());
            for (int j = 2; j <= a / j; j ++) {
                while (a % j == 0) {
                    a /= j;
                    map.put(j, map.getOrDefault(j, 0) + 1);
                }
            }
            if (a > 1) {
                map.put(a, map.getOrDefault(a, 0) + 1);
            }
        }
        
        long ans = 1;
        for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
            long t = 1;
            int a = entry.getKey();
            int b = entry.getValue();
            while (b -- > 0) {
                t = (t * a + 1) % mod;
            }
            ans = ans * t % mod;
        }
        System.out.println(ans);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值