几乎是质数
思路
题目在说什么?给定正整数 ,求区间
中恰好有两个不同质因子的数的个数。比如
有两个不同质因子,是"几乎是质数";
只有一个质因子,不算;
有三个,也不算。
那怎么高效统计每个数有多少个不同的质因子呢?
关键观察
暴力对每个数做质因数分解是 ,太慢。换个角度想:与其"对每个数找它的质因子",不如对每个质数,标记它是谁的因子。
这就是埃氏筛的思路:枚举每个质数 ,把
这些倍数的"不同质因子计数"都加 1。筛完之后,计数恰好为 2 的就是答案。
怎么判断 是质数?如果
还没被任何更小的质数标记过(计数为 0),那它就是质数。
具体做法
- 开一个数组
cnt[i],表示的不同质因子个数,初始为 0
- 从
遍历到
,如果
cnt[i] == 0,说明是质数,遍历它的所有倍数
,令
cnt[j]++ - 最后统计
cnt[i] == 2的个数
代码
#include <bits/stdc++.h>
using namespace std;
int main(){
int n;
scanf("%d", &n);
vector<int> cnt(n + 1, 0);
for(int i = 2; i <= n; i++){
if(cnt[i] == 0){
for(int j = i; j <= n; j += i){
cnt[j]++;
}
}
}
int ans = 0;
for(int i = 2; i <= n; i++){
if(cnt[i] == 2) ans++;
}
printf("%d\n", ans);
return 0;
}
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int[] cnt = new int[n + 1];
for (int i = 2; i <= n; i++) {
if (cnt[i] == 0) {
for (int j = i; j <= n; j += i) {
cnt[j]++;
}
}
}
int ans = 0;
for (int i = 2; i <= n; i++) {
if (cnt[i] == 2) ans++;
}
System.out.println(ans);
}
}
import sys
def solve():
n = int(sys.stdin.readline())
cnt = [0] * (n + 1)
for i in range(2, n + 1):
if cnt[i] == 0:
for j in range(i, n + 1, i):
cnt[j] += 1
ans = 0
for i in range(2, n + 1):
if cnt[i] == 2:
ans += 1
print(ans)
solve()
const readline = require('readline');
const rl = readline.createInterface({ input: process.stdin });
const lines = [];
rl.on('line', line => lines.push(line));
rl.on('close', () => {
const n = parseInt(lines[0].trim());
const cnt = new Int32Array(n + 1);
for (let i = 2; i <= n; i++) {
if (cnt[i] === 0) {
for (let j = i; j <= n; j += i) {
cnt[j]++;
}
}
}
let ans = 0;
for (let i = 2; i <= n; i++) {
if (cnt[i] === 2) ans++;
}
console.log(ans);
});
复杂度分析
- 时间复杂度:
,和埃氏筛一样,每个质数
遍历
个倍数,质数倒数和为
- 空间复杂度:
,存储质因子计数数组
总结
这道题的核心是把"统计每个数的不同质因子个数"转化为埃氏筛的变体。标准的埃氏筛是标记合数,这里改成对每个质数的倍数做计数累加。筛完后扫一遍数组,计数为 2 的就是"几乎是质数"。思路简洁,代码也很短。



京公网安备 11010502036488号