第 1 节的程序只做一次加法,这一节让它做一百万次。循环要想清楚三件事:从哪开始、什么时候停、每轮做什么;累加只想一件事:这个和最大能到多少。前者决定快不快(TLE),后者决定对不对(WA)。
两个数先摆着:1+2+…+65536 = 2147516416,比 int 上限 2147483647 大 32769,存进 int 就是负数 -2147450880;而本节的例题要循环 183 万次。循环次数和数值范围是两件事。
| 组成 | 含义 / 格式要求 |
|---|---|
for | 两个分号隔出三段,分号不能省。 |
初始化 | 进循环前执行一次,i 只在循环内有效。 |
条件 | 每轮开始前检查。i <= n 配 i 从 1 起是 n 轮。 |
步进 | 每轮末尾执行,这里 ++i 与 i++ 等价。 |
循环体 | 只有一句也写花括号,以后加行不会错。 |
long long sum = 0;
for (int i = 1; i <= 100; ++i) sum += i; // 5050
for (int i = 0; i < 5; ++i) cout << i << " "; // 0 1 2 3 4条件别写 i != n 配 i += 2:步进跨过 n 就永远退不出。
| 组成 | 含义 / 格式要求 |
|---|---|
while | 每轮开始前检查条件,为真再转一圈。 |
何时用 | 次数未知用它,次数已知(数 1..n)用 for。 |
改变量 | 循环体里必须让条件最终变假,否则死循环 → TLE。 |
break | 跳出整个循环,适合“找到就停”。 |
continue | 跳过本轮剩余语句,for 会先做步进。 |
double s = 0; int n = 0;
while (s <= 15) { ++n; s += 1.0 / n; } // 退出时 n = 1835421
int x; while (cin >> x) { if (x < 0) break; cout << x << " "; }++n 和 s += 1.0 / n 换个顺序,答案就差 1。
| 组成 | 含义 / 格式要求 |
|---|---|
初值 0 | 加法的单位元。局部变量不初始化是随机值。 |
类型 | 要装得下最终结果:上界 = 项数 × 单项最大值。 |
计数器 | 只加 1 的累加器,初值也是 0。 |
求积 | 初值是 1,写成 0 结果永远为 0。 |
long long sum = 0;
for (int i = 1; i <= n; ++i) sum += i; // n = 1e9 时约 5×10^17
long long prod = 1;
for (int i = 1; i <= 20; ++i) prod *= i; // 20! = 2432902008176640000求和 0、求积 1、求最小值 INT_MAX,都是让第一次运算不改变结果。
| 组成 | 含义 / 格式要求 |
|---|---|
INT_MAX | 2147483647,求最小值的初值。 |
INT_MIN | -2147483648,求最大值的初值。 |
LLONG_MAX | 9223372036854775807,19 位。 |
用法 | 编译期常量,可直接输出。 |
只当边界 | 别做运算:INT_MAX + 1 溢出成 INT_MIN。 |
int mn = INT_MAX;
for (int i = 1; i <= n; ++i) { int x; cin >> x; if (x < mn) mn = x; }
cout << INT_MAX << " " << LLONG_MAX << "\n"; // 2147483647 9223372036854775807两个上限都是 2 的幂减一,末位一定是 7。记“19 位”比记数字实用。
| 组成 | 含义 / 格式要求 |
|---|---|
%lld | long long 的格式符,两个 l 表示 64 位。 |
%d | int 的。给 long long 用只取低 32 位,输出垃圾。 |
%.2f | double 保留两位小数。 |
\n | printf 不会自动换行。 |
别混用 | 关掉 sync_with_stdio 后不要混用 printf 和 cout。 |
long long s = 5000050000LL;
printf("%lld\n", s); // 5000050000
printf("%d\n", (int)s); // 705082704 ← 截断后的残值洛谷是 64 位 Linux,%d 读 long long 静默截断:不报错,只 WA。
五件东西拼起来,够做一道真题了。
已知 S_n = 1 + 1/2 + … + 1/n。n 足够大时,S_n 能超过任意给定的整数 k。
给定 k(1 ≤ k ≤ 15),求最小的 n,使 S_n > k。
输入格式:一行一个正整数 k。输出格式:一行一个正整数 n。
部分和没有公式,只能一次加一项:一个 double 累加器 + 一个次数未知的 while。
停的条件写 s <= k:还没超过就继续。题目要 S_n > k,等于 k 时还得再加一项。
先 ++n 再 s += 1.0 / n,让“加进去的项”和“当前的 n”始终对应;反过来写,输出得改成 n−1。
手算验证:k = 1 时 S_1 = 1 不够、S_2 = 1.5 够,答案 2;k = 2 时 S_3 ≈ 1.833、S_4 ≈ 2.083,答案 4。
#include <bits/stdc++.h>
using namespace std;
int main() {
int k;
cin >> k; // 1 <= k <= 15
double s = 0.0; // 累加器:部分和,必须是浮点
int n = 0; // 已经加了多少项
while (s <= k) { // 还没超过 k,就再补一项
++n; // 先定下“这一轮加第 n 项”,顺序不能反
s += 1.0 / n; // 写 1.0 才是浮点除法;写 1 / n 会永远停不下来
}
cout << n << "\n"; // 此时 s 第一次超过 k,n 就是最小解
return 0;
}| 代码片段 | 含义与理由 |
|---|---|
#include <bits/stdc++.h> | 万能头,顶掉 iostream、climits 等。 |
int k; | 1 ≤ k ≤ 15,int 足够。 |
double s = 0.0; | 累加器。0.0 表示“一项都没加”,也提醒这是浮点。 |
int n = 0; | 项数从 0 起步,进循环第一件事是把它变 1。 |
while (s <= k) | s 没超过 k 就继续;退出时 n 恰是满足条件的最小值。 |
++n; | 先确定这一轮加第几项。放到下一行后面,最后 n 会多 1。 |
s += 1.0 / n; | 写 1.0 才触发浮点除法;写 1 / n 时 n ≥ 2 恒为 0,s 停在 1.0,循环退不出来 → TLE。 |
cout << n << "\n"; | 输出项数并换行。 |
k = 1 → 2;k = 2 → 4;k = 15 → 1835421(真跑 183 万次循环)。
k = 15 只差 1,几乎一定是 ++n 与 s += ... 的顺序问题。
这道题的累加器是 double,不怕溢出。换成求和题,int 就会翻脸。判断只要一个乘法:项数 × 单项最大值,超过 2.1×10⁹ 就用 long long。
| 最坏情况 | 上界 | 类型 |
|---|---|---|
| 累加 10⁵ 个数,每个 ≤ 10⁹ | 10¹⁴ | long long |
| 累加 1..n,n = 65536 | 2147516416 | long long |
| 累加 1..100 | 5050 | int |
| 两个 ≤ 10⁹ 的数相乘 | 10¹⁸ | long long,且要先转 |
| 累乘 1..n,n = 13 | 6227020800 | long long |
int s = 0;
for (int i = 1; i <= 65536; ++i) s += i;
cout << s << "\n"; // -2147450880 ← 溢出成负数,编译器毫无提示
int a = 100000, b = 100000;
cout << a * b << "\n"; // 1410065408 ← 先按 32 位乘出残值
cout << 1LL * a * b << "\n"; // 10000000000 ← 1LL 把表达式抬成 64 位累加器不初始化。long long s; 后直接 s += x,局部变量是随机值,小数据过、大数据 WA,且每次跑结果都不同。
用 1 / n 代替 1.0 / n:每项都是 0,累加器不涨,循环退不出 → TLE。
求和用 int:1+2+…+65536 得负数 → WA。项数 × 单项最大值 > 2.1×10⁹ 就换 long long。
long long ans = a * b; 两边都是 int,先溢出再赋值 → WA,要写 1LL * a * b。
边界差一次:i = 1; i < n 只有 n−1 轮 → 本节的题直接 WA。
关掉页面,从空文件把 P1035 敲一遍,用 k = 1、2、15 三组自测,再提交。
改一改:把 double s = 0.0; 改成 int s = 0;,1.0 / n 改成 1 / n,跑 k = 1——程序不输出任何东西,它在死循环。Ctrl+C 打断,然后回答:n = 2 时 1 / 2 等于几?
再改一改:写 1+2+…+n,先用 int 输入 65536 看负数,再换 long long 看 2147516416。
第一个数字是 2147483647。int 占 32 位:1 位符号 + 31 位数值,正数上限就是 2³¹ − 1,10 位数。所以十位以内基本装得下,十一位以上一定装不下。long long 用 63 位,上限 2⁶³ − 1,19 位。
第二个是调和级数 S_n = 1 + 1/2 + … + 1/n ≈ ln n + γ,γ ≈ 0.5772。反解 S_n = k 得 n ≈ e^(k−0.5772):k = 15 时 n ≈ e^14.4228 ≈ 1.84×10⁶,正是答案 1835421。
| k | n ≈ e^(k−0.5772) | 循环次数 |
|---|---|---|
| 1 | 2 | 2 |
| 5 | 83 | 8.3×10¹ |
| 10 | 12367 | 1.2×10⁴ |
| 15 | 1.84×10⁶ | 1.8×10⁶ |
两个推论。① 时间 = 循环次数 × 每轮工作量,每秒约 10⁸ 次简单操作,10⁶ 安全、10⁹ 危险。② 范围 = 项数 × 单项最大值,超过 2.1×10⁹ 就换 long long。写代码前把这两个乘法各做一遍,就是所谓的“条件反射”。
double 只有 15~16 位有效数字,超过 10¹⁶ 的整数存不准,精确的整数答案永远用 long long。本例敢用 double,是因为每加一项只涨 1/(n+1) ≈ 5.4×10⁻⁷,而总舍入误差只有 10⁻⁹ 量级。
for (n = 1; s <= k; ++n) s += 1.0 / n;,输出写 n 还是 n−1?差一从哪来?1 / n 与 1.0 / n 的区别,以及前者为什么让本节例题死循环。已知正整数 n 是两个不同质数的乘积,求较大的那个质数。
输入格式:一行一个整数 n(2 ≤ n ≤ 2×10⁹)。输出格式:一行一个整数,即较大的质因数。
样例:输入 21,输出 7(21 = 3 × 7)。
别真去分解质因数:n 最大 2×10⁹,从 2 试到 n 要跑十亿次,必 TLE。
若 i 整除 n,则 i 与 n/i 成对出现,这一对里总有一个不超过 √n。
两个质数中较小的那个 ≤ √n。从 2 往上试,第一个命中的约数就是它。
设 n = p × q,p < q 且都是质数。由 p < q 得 p × p < n,即 p < √n。
从 i = 2 递增,第一个满足 n % i == 0 的 i 必是 p:若 i 是合数,它必有更小的质因子,早就被找到了。
答案就是 n / p,输出后直接 return。循环写 i * i <= n,只需试到 √n ≈ 4.5×10⁴ 次。
#include <bits/stdc++.h>
using namespace std;
int main() {
long long n;
cin >> n;
for (long long i = 2; i * i <= n; ++i) {
if (n % i == 0) { // 从小到大,第一个约数就是较小的质因数
cout << n / i << "\n"; // 另一个因子更大,正是答案
return 0; // 找到即停
}
}
return 0;
}| 片段 | 作用 |
|---|---|
long long n; | n 最大 2×10⁹,int 装得下,用 long long 是为了后面的除法不贴边。 |
long long i = 2 | 循环变量也取 64 位,否则 i * i 按 32 位算;数据到 10¹⁸ 就会溢出。 |
i * i <= n | 试到 √n 为止。写 i <= sqrt(n) 也行,但开方有浮点误差,乘法更可靠。 |
n % i == 0 | 第一个命中的 i 就是较小的质数 p,n / i 就是较大的 q。 |