题目1:判断较小的数字是否是质数
一个数字n,从 2 开始到 根号n,看看这些数字能否被n整除即可,时间复杂度O(根号n)
public static boolean isPrime(long n) {
if (n <= 1) {
return false;
}
// 2 ... 根号n
for (long i = 2; i * i <= n; i++) {
if (n % i == 0) {
return false;
}
}
return true;
}
题目2:判断较大的数字是否是质数(Miller-Rabin测试)
测试链接 : https://www.luogu.com.cn/problem/U148828
判断n是否是质数,Miller-Rabin测试大概过程:
1,每次选择1 ~ n-1范围上的随机数字,或者指定一个比n小的质数,进行测试
2,测试过程的数学原理不用纠结,不重要,因为该原理除了判断质数以外,不再用于别的方面
3,原理:费马小定理、Carmichael(卡米切尔数)、二次探测定理(算法导论31章)、乘法同余、快速幂
4,经过s次Miller-Rabin测试,s越大出错几率越低,但是速度也会越慢,一般测试20次以内即可
重点是用法
因为有乘法同余,所以想验证任意的long类型的数字,需要注意位数的事情
代码中都标记好了,我们说明一下:java模版、 C++模版
时间复杂度O( s * (logn)的三次方 ),速度很快
<=10^9
package class097;
// 判断较大的数字是否是质数(Miller-Rabin测试)
// 测试链接 : https://www.luogu.com.cn/problem/U148828
// 如下代码无法通过所有测试用例
// 本文件可以解决10^9范围内数字的质数检查
// 时间复杂度O(s * (logn)的三次方),很快
// 为什么不能搞定所有long类型的数字检查
// 原因在于long类型位数不够,乘法同余的时候会溢出,课上已经做了说明
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
public class Code02_LargeNumberIsPrime1 {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
int t = Integer.valueOf(br.readLine());
for (int i = 0; i < t; i++) {
// 这里要注意
// 如果读入long类型的数字,数字本身很大的话
// 先读出字符串str,然后用Long.valueOf(str)
long n = Long.valueOf(br.readLine());
out.println(millerRabin(n) ? "Yes" : "No");
}
out.flush();
out.close();
br.close();
}
// 质数的个数代表测试次数
// 如果想增加测试次数就继续增加更大的质数
public static long[] p = { 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37 };
public static boolean millerRabin(long n) {
if (n <= 2) {
return n == 2;
}
if ((n & 1) == 0) {
return false;
}
for (int i = 0; i < p.length && p[i] < n; i++) {
if (witness(p[i], n)) {
return false;
}
}
return true;
}
// 单次测试的函数,不用纠结原理
// 返回n是不是合数
public static boolean witness(long a, long n) {
long u = n - 1;
int t = 0;
while ((u & 1) == 0) {
t++;
u >>= 1;
}
long x1 = power(a, u, n), x2;
for (int i = 1; i <= t; i++) {
x2 = power(x1, 2, n);
if (x2 == 1 && x1 != 1 && x1 != n - 1) {
return true;
}
x1 = x2;
}
if (x1 != 1) {
return true;
}
return false;
}
// 返回 : n的p次方 % mod
// 快速幂,讲解098会重点讲述,此时直接用即可
public static long power(long n, long p, long mod) {
long ans = 1;
while (p > 0) {
if ((p & 1) == 1) {
ans = (ans * n) % mod;
}
n = (n * n) % mod;
p >>= 1;
}
return ans;
}
}
大于10^9
package class097;
// 判断较大的数字是否是质数(Miller-Rabin测试)
// 测试链接 : https://www.luogu.com.cn/problem/U148828
// 请同学们务必参考如下代码中关于输入、输出的处理
// 这是输入输出处理效率很高的写法
// 提交以下的code,提交时请把类名改成"Main",可以通过所有测试用例
// 本文件可以搞定任意范围数字的质数检查,时间复杂度O(s * (logn)的三次方)
// 为什么不自己写,为什么要用BigInteger中的isProbablePrime方法
// 原因在于long类型位数不够,乘法同余的时候会溢出,课上已经做了说明
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.math.BigInteger;
public class Code02_LargeNumberIsPrime2 {
// 测试次数,次数越多失误率越低,但速度也越慢
public static int s = 10;
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
int t = Integer.valueOf(br.readLine());
for (int i = 0; i < t; i++) {
BigInteger n = new BigInteger(br.readLine());
// isProbablePrime方法包含MillerRabin和LucasLehmer测试
// 给定测试次数s即可
out.println(n.isProbablePrime(s) ? "Yes" : "No");
}
out.flush();
out.close();
br.close();
}
}
较大数字读入
package class097;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.StreamTokenizer;
// 读入long类型数字的注意点
// 讲解019的扩展,没有看过讲解019的同学去看一下
public class Code02_InputLong {
public static void main(String[] args) throws IOException {
f1();
f2();
}
public static void f1() throws IOException {
System.out.println("f1函数测试读入");
// 尝试读入 : 131237128371723187
// in.nval读出的是double类型
// double类型64位
// long类型也是64位
// double的64位会分配若干位去表达小数部分
// long类型的64位全用来表达整数部分
// 所以读入是long范围的数,如果用以下的写法
// in.nval会先变成double类型,如果再转成long类型,就可能有精度损耗
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StreamTokenizer in = new StreamTokenizer(br);
PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
in.nextToken();
long num = (long) in.nval;
out.println(num);
out.flush();
}
public static void f2() throws IOException {
System.out.println("f2函数测试读入");
// 尝试读入 : 131237128371723187
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
// 直接读出字符串
String str = br.readLine();
// 然后把字符串转成long
// 不可能有精度损耗
long num = Long.valueOf(str);
out.println(num);
out.flush();
}
}
题目3:质因子分解
时间复杂度O(根号n)
有关质因数分解,课上讲的方法足够了
有兴趣的同学可以继续研究
pollard_rho启发式方法分解质因数
void f(int n) {
for (int j = 2; j * j <= n; j++) {
if (n % j == 0) {
print(j);
while (n % j == 0) { n /= j; }
}
}
if (n > 1) {
print(n);
}
}
题目4:按公因数计算最大组件大小
给定一个由不同正整数的组成的非空数组 nums
如果 nums[i] 和 nums[j] 有一个大于1的公因子,那么这两个数之间有一条无向边
返回 nums中最大连通组件的大小
测试链接 : https://leetcode.cn/problems/largest-component-size-by-common-factor/
class Solution {
public:
vector<int> p,s;
int find(int x){
if(x!=p[x])p[x]=find(p[x]);
return p[x];
}
int largestComponentSize(vector<int>& nums) {
int n=nums.size();
for(int i=0;i<n;i++){
p.push_back(i);
s.push_back(1);
}
unordered_map<int,vector<int>> q;
for(int i=0;i<n;i++){
int x=nums[i];
for(int j=1;j*j<=x;j++){
if(x%j==0){
if(j>1) q[j].push_back(i);
q[x/j].push_back(i);
}
}
}
int res=1;
for(auto [k,v]:q){
for(int i=1;i<v.size();i++){
int a=v[0],b=v[i];
if(find(a)!=find(b)){
s[find(a)]+=s[find(b)];
p[find(b)]=find(a);
res=max(res,s[find(a)]);
}
}
}
return res;
}
};
题目5:给定整数n,返回 1~n 范围上所有的质数
埃氏筛,时间复杂度O(n * log(logn)),我们图解一下
欧拉筛,时间复杂度O(n),很精妙,我们图解一下
其实掌握埃氏筛足够了,因为时间复杂度非常接近线性了,而且常数时间很不错
给定整数n,返回 小于n 的质数的数量
测试链接 : https://leetcode.cn/problems/count-primes/
public static int countPrimes(int n) {
return ehrlich(n - 1);
}
// 埃氏筛统计0 ~ n范围内的质数个数
// 时间复杂度O(n * log(logn))
public static int ehrlich(int n) {
// visit[i] = true,代表i是合数
// visit[i] = false,代表i是质数
// 初始时认为0~n所有数都是质数
boolean[] visit = new boolean[n + 1];
for (int i = 2; i * i <= n; i++) {
if (!visit[i]) {
for (int j = i * i; j <= n; j += i) {
visit[j] = true;
}
}
}
int cnt = 0;
for (int i = 2; i <= n; i++) {
if (!visit[i]) {
// 此时i就是质数,可以收集,也可以计数
cnt++;
}
}
return cnt;
}
// 欧拉筛统计0 ~ n范围内的质数个数,防止重复,埃氏筛法优化
// 时间复杂度O(n)
public static int euler(int n) {
// visit[i] = true,代表i是合数
// visit[i] = false,代表i是质数
// 初始时认为0~n所有数都是质数
boolean[] visit = new boolean[n + 1];
// prime收集所有的质数,收集的个数是cnt
int[] prime = new int[n / 2 + 1];
int cnt = 0;
for (int i = 2; i <= n; i++) {
if (!visit[i]) {
prime[cnt++] = i;
}
for (int j = 0; j < cnt; j++) {
if (i * prime[j] > n) {
break;
}
visit[i * prime[j]] = true;
if (i % prime[j] == 0) {
break;
}
}
}
return cnt;
}
// 只是计数的话
// 埃氏筛还能改进
public static int ehrlich2(int n) {
if (n <= 1) {
return 0;
}
// visit[i] = true,代表i是合数
// visit[i] = false,代表i是质数
// 初始时认为0~n所有数都是质数
boolean[] visit = new boolean[n + 1];
// 先把所有的偶数去掉,但是算上2
// 估计的质数数量,如果发现更多合数,那么cnt--
int cnt = (n + 1) / 2;
for (int i = 3; i * i <= n; i += 2) {
if (!visit[i]) {
for (int j = i * i; j <= n; j += 2 * i) {
if (!visit[j]) {
visit[j] = true;
cnt--;
}
}
}
}
return cnt;
}

被折叠的 条评论
为什么被折叠?



