下述代码实现素数表的线性筛法,筛选出所有小于等于 $n$ 的素数。下面说法正确的是( )。
vector<int> sieve_linear(int n) {
vector<bool> is_prime(n +1, true);
vector<int> primes;
for (int i = 2; i <= n/2; i++) {
if (is_prime[i])
primes.push_back(i);
for (int j = 0; j < primes.size() && i * primes[j] <= n; j++) {
is_prime[ i * primes[j] ] = 0;
if (i % primes[j] == 0)
break;
}
}
for (int i = n/2 +1; i <= n; i++) {
if (is_prime[i])
primes.push_back(i);
}
return primes;
}埃氏筛法:从2开始标记倍数为合数,O(n log log n)。线性筛(欧拉筛):每个合数只被最小质因子标记一次,O(n)。线性筛效率更高。
分析复杂度时注意外层循环和内层循环的关联。例如内外两层都到n的循环不一定是O(n²),要看内层是否每次都在缩小。
【考点】线性筛法|时间复杂度分析|埃氏筛对比
解题思维:代码本质是线性筛的变体——外层循环到 n/2 时,每个合数 i*primes[j] 仅当 primes[j] 是其最小质因子时才会被标记,若 i%primes[j]==0 则立即 break,保证每个合数只被筛一次。最后 n/2+1~n 的补充循环只是补上大质数,不影响复杂度。
陷阱拆解:A 正确,线性筛复杂度线性 O(n);B 错,每个合数仅被最小质因子筛一次,而非所有质因子;C 错,埃氏筛会重复标记(如 12 被 2 和 3 各筛一次),与线性筛核心思想不同;D 显然排除。
反命题思维:命题人刻意用 n/2 分界拆分循环,想考验你是否真正理解线性筛的"每个合数唯一标记"机制,而非机械记忆复杂度。改题:若把外层循环改为 i<=n,则时间复杂度仍为 O(n),但内层标记逻辑不变。