1 条题解
-
0
思路
素数(质数)是大于 1、且只能被 1 和它本身整除的数。所以 0、1 和所有负数都直接判 No。
判断一个数 n 是不是素数,最直接的方法是试除法:用 2, 3, 4, … 逐个去试,看有没有数能整除 n。但不用试到 n,试到 √n 就够了:
如果 n 有一个大于 √n 的因数 d,那么 n/d 一定是一个小于 √n 的因数——早就被试出来了。所以试到 √n 还没找到因数,n 一定是素数。
多组输入的写法:
while (scanf("%d", &x) != EOF),读到文件末尾自动停止。代码(正确版)
#include <stdio.h> int isPrime(int n) { if (n < 2) return 0; // 0、1、负数都不是素数 for (int i = 2; i <= n / i; i++) // 用除法代替 i*i<=n,避免溢出 if (n % i == 0) return 0; return 1; } int main() { int x; while (scanf("%d", &x) != EOF) { if (isPrime(x)) printf("Yes\n"); else printf("No\n"); } return 0; }关键坑:
i * i <= n会溢出!很多人(包括不少教材)把循环条件写成:
for (int i = 2; i * i <= n; i++)在 n 不大时完全正确。但当 n 接近 int 上限(2147483647)且 n 是质数时,循环会一路走到
i = 46341,此时:i * i = 46341 × 46341 = 2147488281,超过 int 最大值 2147483647;- 溢出后
i * i变成负数(-2147479015),条件"负数 ≤ n"永远成立; - 而 n 是质数,
n % i永远不为 0,函数永远退不出循环 → 程序超时(TLE)。
方法一(本代码采用):改用除法条件
i <= n / i它与
i * i <= n数学上等价(两边同乘 i 变形),但只有除法和比较,永远不会溢出。注意写成i <= n / i而不是n / i >= i之外的任何变形,含义相同。方法二:循环变量用 long long
for (long long i = 2; i * i <= n; i++)long long上限约 9.2×10¹⁸,i * i最多 46341² ≈ 2.1×10⁹,绰绰有余。缺点是取模运算n % i混用了 int 和 long long,会有隐式类型转换(不影响正确性)。两种都行,方法一改动更小、也更锻炼对溢出的理解。
也可以:为什么不用
sqrt(n)?for (int i = 2; i <= sqrt(n); i++) // 不推荐sqrt是浮点运算,对大数可能有微小误差(比如把 46340.9999 算成 46341.0 或反过来),导致循环次数差一、判断出错。如果一定要用,需写成i <= (int)sqrt(n + 0.5)来纠偏。既然i <= n / i又快又准,没必要冒浮点的险。样例演算
输入
4:i = 2 时4 % 2 == 0→ 返回 0 → 输出 No。输入
7:i i <= 7/i? 7 % i 2 2 <= 3 ✓ 1 3 3 <= 2 ✗ 退出 — 循环结束没找到因数 → 返回 1 → 输出 Yes。
易错点汇总
- 小于 2 的数(0、1、负数)都不是素数,开头必须拦截,否则 1 会被误判为素数。
i * i <= n在 n 接近 int 上限时溢出,导致质数判成死循环(详见上文);用i <= n / i或 long long。- 循环条件是"小于等于":
i <= n / i,写错一个等号可能漏判完全平方数(如 n = 4 时 i = 2 恰好是 √n)。 - 多组输入用
scanf(...) != EOF,别写死循环次数。
- 1
信息
- ID
- 19
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 2
- 标签
- (无)
- 递交数
- 4
- 已通过
- 1
- 上传者
吉公网安备22010402001496号