本文接上一篇研究生机试算法。在讨论完几个经典入门算法后,我们来开始系统地学习研究生机试算法。
最大公约数
当我们遇到诸如求最大公约数、最小公倍数之类的数学问题时,大多数人第一时间会想到暴力枚举求解,因为我们并没有系统地学习过这方面的数学知识。所以,当我们查阅资料,我们可以发现,使用欧几里得算法可以使算法更加高效。过程如下:
若a,b全为零,则它们的最大公约数不存在;若a、b其中之一为零,则它们的最大公约数为a、b中非零的那个;若a、b都不为零,则使新a=b;新b=a%b,然后重复该过程。
这就是我们要介绍的欧几里得算法,它改变了上文提到的枚举算法所需要的情况,将算法规模大大缩小。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package com.algorithm.math;
import java.util.Scanner;
/**
* @author fzm
* @date 2019-02-23
*/
public class GCD {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int a, b;
while (sc.hasNextInt()) {
a = sc.nextInt();
b = sc.nextInt();
System.out.println(gcd(a, b));
sc.nextLine();
}
}
private static int gcd(int a, int b) {
if (b == 0) return a;
else {
return gcd(b, a % b);
}
}
}这就是求最大公约数的算法,同样也可以使用非递归形式。
最小公倍数
- a、b的最小公倍数为
a*b/k
,k为a、b的最大公倍数,因此可以使用上述算法求解。
高精度整数
- 如果计算的数据大于int(long)所能表示的最大范围,那么我们需要使用高精度整数类BigInteger,它里面内置了四则运算的API,只要考前查阅即可。
分解质因数
顾名思义,对一个数
x
分解质因数即确定素数$p_1p_2p_3$,使其满足,在特定条件下我们还要确定$e_1、e_2$等幂指数。我们来看一道求质因数的个数的题(2007年清华大学研究生机试题),要求我们求出一个正整数N的质因数的个数,(1<N<10^9)。
1 | package com.algorithm.math; |
组合数
关于n!的问题
n!表示n的阶乘,并且有
n!=1 * 2 * 3 * … * n
成立,我们讨论一下关于它的问题,求n!中有多少个质因子p。如果我们先求n!在按上述计算,那么数据可能非常大,根本无法计算出来,因此我们需要更快的解。我们以10!为例,显然其中有因子2的个数为5,$2^2$的个数为2,$2^3$的个数为1,因此共有8个质因子2。仔细思考变可以发现此过程可以推广为n!中有$(\frac{n}{p}+\frac{n}{p^2}+\frac{n}{p^3}+…)$个质因子p,其中除法均为向下取整。于是便得到了O(logn)算法,代码如下:
1
2
3
4
5
6
7
8int cal(int n, int p){
int ans = 0;
while(n){
ans += n / p;
n /= p;
}
return ans;
}利用这个算法,可以很快计算出n!的末尾有多少个零,只要带入
cal(n, 5)
即可。
组合数的计算
组合数$C_n^m$是指从n个不同元素中选出m个元素的方案数(m≤n),其定义式为$C_n^m=\frac{n!}{m!(n-m)!}$,由三个整数的阶乘得到。通过定义可以知道,组合数满足$C_n^m=C_n^{n-m}$,且有$C_n^0=C_n^n=1$成立。本节讨论如下两个问题:
- 如何计算$C_n^m$。
- 如何计算${C_n^m}$%p。
第一个问题
方法一:定义法
- 通过定义式来算,先求阶乘,再除法,这样阶乘相当庞大,因此只能接受50以内的计算。
方法二:递推
- 我们可以得到组合数的递推公式:
从直观上看,公式总是把n减一,而把m保持原样或减一,这样这个递推公式最终总可以把n和m变成相同或是让m变为0,而又有$C_n^0=C_n^n=1$,这正好可以作为递归边界。代码如下:
1 | long long C(long long n, long long m){ |
方法三:定义式变形
- 我们观察该式:
这样,就可以写出相应的代码了,代码如下:
1 | long long C(long long n, long long m){ |
第二个问题
方法一:递推取模
- 这种方法基于第一个问题的方法二,是最容易实现的一种。只要在原先的基础上适当的得房对p取模即可。
方法二:卢卡斯定理
- 如果p是素数,将m和n表示为p进制:
那么卢卡斯定理告诉我们,成立。例如对$C_8^3 mod 5$来说,有
于是有$C_8^3mod5=C_1^0×C_3^3mod5=1$。卢卡斯定理的证明不在此处,我们直接给出代码,并且为了看起来清晰,此处把p
设为全局变量,不作为参数传来传去。
1 | int Lucas(int n, int m){ |