【深基4.例13】质数口袋 的题解
记住只在没有思路时使用题解,不要从它复制粘贴代码。请尊重题目和题解的作者。
在解题之前提交题解的代码会导致封禁。
在解题之前提交题解的代码会导致封禁。
作者:
概述
本题要求从 开始依次把质数装入“质数口袋”,装入的质数之和不能超过
,在此前提下使装入的质数个数最多,最后按从小到大的顺序输出装入的质数并输出个数。核心解法是用筛法预处理出所有不超过
的质数,再按升序贪心累加,直到加上下一个质数会超过
为止。
分析
核心观察
质数是按升序依次装入口袋的,所以任意一个可行方案都对应质数序列的一个前缀,要让个数最多就必须优先装入最小的质数。设不超过 的质数从小到大为
,则对任意
而言,前
个质数之和是所有大小为
的质数集合中和最小者。
思路
由核心观察可知,能装下 个质数当且仅当
因此答案就是使该不等式成立的最大整数 。由于质数严格递增,前缀和也严格递增,只需从最小的质数开始逐个试加:维护当前已装入的和
与个数
,遇到质数
时若
就把它装入口袋,否则立即停止,因为后面更大的质数只会让和超出更多。
质数的筛选方式决定了整体复杂度。朴素做法对每个数单独试除判断质数,单次判定需要 时间,总复杂度为
,在本题范围内虽可接受但不必要;改用埃氏筛只需
时间就能一次性筛出全部质数,之后顺序扫描一遍即可完成贪心,贪心的正确性由核心观察保证。
具体示例
以 为例,从最小的质数开始依次累加:
此时和恰好等于 ,仍满足“不超过
”的限制;下一个质数是
,而
,超出承重量,于是停止装入。最终装入的质数为
共
个,先逐行输出这
个质数,再输出个数
。
算法步骤
- 读入
。
- 建立标记数组
isPrime并全部置为真,再把下标与下标
置为假。
- 从
开始枚举
i,当时结束;若
isPrime[i]为真,则把到
之间所有
i的倍数标记为假。 - 初始化累加和
total与计数cnt均为,从
到
顺序枚举
i。 - 若
isPrime[i]为假则跳过;对质数i,若则结束枚举,否则输出
i,令total加上i、cnt加。
- 输出
cnt。
复杂度分析
- 时间:
,埃氏筛标记合数的代价,贪心扫描为
,被筛法代价吸收。
- 空间:
,布尔标记数组的长度为
。
实现注意事项
时没有任何质数能装入口袋,此时不输出任何质数,只输出个数
这一行,不能因为个数是
而漏掉输出。
- 筛法必须把下标
与下标
显式标记为非质数,
既不是质数也不是合数,不能沿用初始值。
- 停止条件是
而非
:和恰好等于
时仍然可以装入。
- 输出共
行,前
行是装入的质数,最后一行是质数个数,每个数字各占一行并以换行结尾。
- 累加和按构造始终不超过
,用
位整数存放即可;标记数组需要长度为
,否则访问下标
时会越界。
- 埃氏筛内层循环从
开始标记,因为小于
的倍数已被更小的质数标记过,从
开始可以省去重复工作。
源代码
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int L;
cin >> L;
vector<bool> isPrime(L + 1, true);
isPrime[0] = isPrime[1] = false;
for (int i = 2; i * i <= L; ++i) {
if (!isPrime[i]) {
continue;
}
for (int j = i * i; j <= L; j += i) {
isPrime[j] = false;
}
}
int total = 0, cnt = 0;
for (int i = 2; i <= L; ++i) {
if (!isPrime[i]) {
continue;
}
if (total + i > L) {
break;
}
total += i;
++cnt;
cout << i << '\n';
}
cout << cnt << '\n';
return 0;
}
import sys
def main():
data = sys.stdin.read().split()
L = int(data[0])
is_prime = [True] * (L + 1)
is_prime[0] = is_prime[1] = False
for i in range(2, int(L ** 0.5) + 1):
if not is_prime[i]:
continue
for j in range(i * i, L + 1, i):
is_prime[j] = False
total = 0
cnt = 0
out = []
for i in range(2, L + 1):
if not is_prime[i]:
continue
if total + i > L:
break
total += i
cnt += 1
out.append(str(i))
out.append(str(cnt))
sys.stdout.write("\n".join(out) + "\n")
main()
评论