Basic·Bài B2

if and else

Một chương trình chỉ gồm cin, vài phép tính và cout chạy cùng một dãy lệnh cho mọi dữ liệu. Phần lớn đề bài thì không thế: output phụ thuộc vào input, như in YES nếu một số thỏa điều kiện và NO nếu không, hay in số lớn hơn trong hai số. Lệnh if (rẽ nhánh, branching) cho chương trình tự chọn: kiểm tra một điều kiện, rồi mới quyết định có chạy một đoạn lệnh hay không.

Bài này đi từ một nhánh tới nhiều nhánh, và dừng lại ở từng chỗ người mới hay vấp: = thay cho ==, dấu chấm phẩy thừa, thứ tự các nhánh, số dư của số âm, phép chia nguyên.

Lệnh if: chạy hoặc bỏ qua

Ở bài B1, các lệnh chạy từ trên xuống dưới, mỗi lệnh đúng một lần. Lệnh if thêm khả năng bỏ qua: đoạn lệnh nằm trong if chỉ chạy khi điều kiện đúng. Chương trình dưới đọc nhiệt độ tt (độ C, số nguyên) và cảnh báo khi t≥38t \ge 38.

C++
#include <bits/stdc++.h>
using namespace std;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);

    int t;
    cin >> t;
    if (t >= 38) {
        cout << "FEVER\n";
    }
    cout << "DONE\n";
}
39

Điều kiện (condition) nằm trong cặp ngoặc tròn; t >= 38 là một phép so sánh, kết quả chỉ có hai khả năng là đúng hoặc sai. Cặp ngoặc nhọn gói các lệnh thuộc về if, gọi là một khối lệnh (block): điều kiện đúng thì cả khối chạy, sai thì cả khối bị nhảy qua. Dòng cout << "DONE\n"; nằm ngoài khối nên luôn chạy; với t=36t = 36 chương trình chỉ in DONE. Mỗi lệnh bên trong khối vẫn kết thúc bằng dấu chấm phẩy, còn dòng if (...) và dấu } thì không.

Sáu phép so sánh dùng trong điều kiện:

Viết Đọc là
a < b aa nhỏ hơn bb
a <= b aa nhỏ hơn hoặc bằng bb
a > b aa lớn hơn bb
a >= b aa lớn hơn hoặc bằng bb
a == b aa bằng bb
a != b aa khác bb

Dấu = luôn đứng bên phải trong <= và >=; viết =< hay => là lỗi. == và != dùng được cả cho char và string, như c == 'a' hay s == "YES".

Kết quả của một phép so sánh là đúng hoặc sai (kiểu bool). Khi in ra hay đem đi tính, đúng là số 1 và sai là số 0, nên cout << (5 > 3); in 1. Ngược lại, khi một số được dùng làm điều kiện thì 0 là sai và mọi số khác 0 là đúng. Chiều ngược này giải thích lỗi nổi tiếng nhất.

Một dấu = và hai dấu ==. Dấu = là gán: t = 38 bỏ 38 vào biến t. Dấu == mới là so sánh bằng: t == 38 hỏi t có bằng 38 không và không đổi t. Viết if (t = 38) thì chương trình vẫn biên dịch và chạy: nó gán 38 cho t, lấy chính 38 làm điều kiện, mà 38 khác 0 nên khối luôn chạy, còn t đã bị ghi đè. Biên dịch với g++ -Wall sẽ có cảnh báo cho dòng này, nên tập bật cờ đó khi luyện ở nhà.

Dấu chấm phẩy thừa và thiếu ngoặc nhọn. Hai đoạn dưới đều biên dịch được nhưng chạy sai.

C++
if (t >= 38);
{
    cout << "FEVER\n";
}

if (t >= 38)
    cout << "FEVER\n";
    cout << "CALL DOCTOR\n";

Ở đoạn đầu, dấu ; ngay sau if (t >= 38) là một lệnh rỗng, và đó là toàn bộ thân của if; khối in FEVER nằm ngoài nên luôn chạy. Ở đoạn sau, không có ngoặc nhọn thì if chỉ ôm đúng một lệnh ngay sau nó; thụt lề chỉ để người đọc, nên CALL DOCTOR luôn được in. Hãy luôn viết ngoặc nhọn, trừ khi thân chỉ có một lệnh ngắn viết ngay trên cùng dòng.

Hỏi nhanh

Biến x đang bằng 3, rồi chương trình chạy if (x = 5) { cout << "A"; } cout << x;. Chương trình in ra gì?

Ví dụGiá trị tuyệt đối

Đề. Cho số nguyên xx (−1000≤x≤1000-1000 \le x \le 1000). In khoảng cách từ điểm xx đến điểm 00 trên trục số.

Lời giải. Khoảng cách là xx nếu x≥0x \ge 0, là −x-x nếu x<0x < 0. Chỉ trường hợp âm cần sửa, nên một if không có nhánh nào khác là đủ: nếu x<0x < 0 thì đổi dấu xx, rồi in xx. Với x=0x = 0 điều kiện sai, chương trình in luôn 0. Hàm abs(x) ở cuối bài làm đúng việc này trong một lệnh.

Code đầy đủ
#include <bits/stdc++.h>
using namespace std;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);

    int x;
    cin >> x;
    if (x < 0) {
        x = -x;
    }
    cout << x << '\n';
}
-7

Hai nhánh: if else

Nhiều bài có dạng “đúng thì làm việc này, ngược lại làm việc kia”. Ví dụ một cửa hàng bán bút giá 10 nghìn đồng một chiếc, mua từ 5 chiếc trở lên thì mỗi chiếc chỉ còn 8 nghìn; tính số nghìn đồng phải trả cho nn chiếc.

Viết hai if rời nhau, một cho n >= 5, một cho n < 5, thì phải tự giữ cho hai điều kiện đối nhau tuyệt đối; sửa một chỗ mà quên chỗ kia là hỏng. else gọn hơn: khối sau else chạy khi và chỉ khi điều kiện của if ngay trước nó sai, và không cần viết điều kiện nào nữa.

C++
#include <bits/stdc++.h>
using namespace std;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);

    int n;
    cin >> n;
    int price;
    if (n >= 5) {
        price = 8;
    } else {
        price = 10;
    }
    cout << n * price << '\n';
}
7
Đổi n rồi chạy: chương trình đi qua đúng một trong hai nhánh rồi gặp lại ở lệnh in.

Biến price (giá một chiếc) được khai báo trước if chứ không phải trong từng nhánh, vì biến khai báo bên trong một khối { } chỉ dùng được trong khối đó; ra khỏi dấu } nó biến mất.

Đây cũng là chỗ dễ gặp lỗi biến chưa có giá trị (uninitialized variable). int price; mới khai báo không tự bằng 0; nó mang một giá trị rác còn sót trong bộ nhớ. Bỏ nhánh else đi thì với n<5n < 5 lệnh in dùng một price chưa được gán, và kết quả không đoán trước được: có thể đúng trên máy bạn mà sai trên máy chấm. Quy tắc là mọi đường đi qua các if đều phải gán cho biến, hoặc gán sẵn giá trị mặc định lúc khai báo rồi để if chỉ sửa trường hợp đặc biệt:

C++
int price = 10;
if (n >= 5) {
    price = 8;
}

Ví dụĐủ tiền mua vé

Đề. Một vé giá pp đồng, bạn có mm đồng (0≤m≤10000 \le m \le 1000, 1≤p≤10001 \le p \le 1000). In YES nếu đủ tiền mua một vé, ngược lại in NO.

Lời giải. Đủ tiền nghĩa là m≥pm \ge p, kể cả khi vừa đúng bằng: test ví dụ của đề chính là m=p=20m = p = 20 và đáp án YES. Hai khả năng loại trừ nhau, nên dùng if else. Viết nhầm m > p thì chỉ sai đúng ở ngưỡng m=pm = p, và đó là lý do luôn thử giá trị bằng ngưỡng.

Code đầy đủ
#include <bits/stdc++.h>
using namespace std;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);

    int m, p;
    cin >> m >> p;
    if (m >= p) {
        cout << "YES\n";
    } else {
        cout << "NO\n";
    }
}
20 20

Nhiều nhánh: else if

Khi có ba nhánh trở lên, ta nối else với một if mới. Ví dụ phân loại nhiệt độ ngoài trời: dưới 16 độ là COLD, từ 16 đến 27 độ là MILD, từ 28 độ trở lên là HOT.

C++
#include <bits/stdc++.h>
using namespace std;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);

    int t;
    cin >> t;
    if (t < 16) {
        cout << "COLD\n";
    } else if (t < 28) {
        cout << "MILD\n";
    } else {
        cout << "HOT\n";
    }
}
20

else if không phải từ khóa mới: nó là else mà thân là một lệnh if khác. Cả chuỗi được xét từ trên xuống: điều kiện nào đúng trước thì khối đó chạy, rồi cả chuỗi kết thúc và các nhánh bên dưới không được xét nữa. Với t=20t = 20, t < 16 sai nên đi tiếp, t < 28 đúng nên in MILD, xong.

Từ đó có hai hệ quả. Khi tới nhánh thứ hai ta đã biết chắc t≥16t \ge 16, nên chỉ cần viết t < 28 chứ không cần t >= 16 && t < 28. Và thứ tự các nhánh là một phần của lời giải: các ngưỡng phải xếp theo một chiều nhất quán với phép so sánh, tăng dần khi dùng < như trên, giảm dần khi dùng >=.

Hỏi nhanh

Ta đổi chỗ hai nhánh đầu của chương trình trên: if (t < 28) { cout << "MILD"; } else if (t < 16) { cout << "COLD"; } else { cout << "HOT"; }. Với t=10t = 10 chương trình in ra gì?

Đừng nhầm chuỗi else if với các if viết rời. Ở chuỗi, nhiều nhất một khối chạy; với các if rời, mỗi if tự xét và nhiều khối có thể cùng chạy. Các nhánh loại trừ nhau thì dùng chuỗi; khi mỗi điều kiện đóng góp thêm một phần vào kết quả thì dùng if rời.

Thân của một if cũng có thể chứa if khác: if (A) { if (B) { ... } } chỉ chạy ... khi cả A lẫn B đúng. Chỗ nguy hiểm là else đặt sau hai if lồng nhau mà không có ngoặc nhọn:

C++
if (a > 0)
    if (b > 0)
        cout << "BOTH\n";
else
    cout << "A IS NOT POSITIVE\n";

Thụt lề gợi ý else thuộc về if (a > 0), nhưng else luôn gắn với if gần nhất chưa có else. Ở đây nó thuộc về if (b > 0), nên với a=3a = 3, b=−1b = -1 chương trình in A IS NOT POSITIVE dù aa dương, còn với a≤0a \le 0 thì không in gì. Bọc if bên trong bằng ngoặc nhọn thì else đi với if ngoài đúng như ý định.

C++ còn có lệnh switch để so một biến nguyên với vài giá trị cụ thể. Chuỗi else if làm được mọi việc đó và xử lý được cả khoảng giá trị, nên ta chưa cần tới nó. Đọc code người khác mà gặp switch thì nhớ: thiếu break ở cuối một case thì chương trình chạy tràn xuống case kế tiếp.

Ví dụXếp loại điểm

Đề. Cho điểm nguyên dd từ 0 đến 10. In GOOD nếu d≥8d \ge 8, PASS nếu 5≤d<85 \le d < 8, và TRY_AGAIN nếu d<5d < 5.

Lời giải. Hai ngưỡng 5 và 8 cắt các điểm từ 0 đến 10 thành ba khoảng không giao nhau, nên dùng chuỗi else if, xét từ ngưỡng cao xuống thấp. Nhánh PASS chỉ được xét khi nhánh GOOD sai, tức đã biết d<8d < 8, nên điều kiện d >= 5 ở đó đúng nghĩa 5≤d<85 \le d < 8 và không cần viết lại cận trên. Nhánh else nhận mọi d<5d < 5.

Thử d = 4, 5, 7, 8: chuỗi xét từ trên xuống, gặp điều kiện đúng đầu tiên là dừng.

Viết d > 8 thay cho d >= 8 thì điểm đúng 8 bị xếp vào PASS. Cách kiểm là thử bốn giá trị sát hai ngưỡng, 4, 5, 7, 8, phải ra lần lượt TRY_AGAIN, PASS, PASS, GOOD. Chữ in ra cũng phải đúng TRY_AGAIN, viết hoa và có dấu gạch dưới.

Code đầy đủ
#include <bits/stdc++.h>
using namespace std;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);

    int d;
    cin >> d;
    if (d >= 8) {
        cout << "GOOD\n";
    } else if (d >= 5) {
        cout << "PASS\n";
    } else {
        cout << "TRY_AGAIN\n";
    }
}
7

Chia hết và phép %

Ở B1 ta đã có 17 / 5 bằng 3 và 17 % 5 bằng 2, vì 17=5⋅3+217 = 5 \cdot 3 + 2. Số nn chia hết cho kk khi số dư bằng 0, nên điều kiện kinh điển để kiểm tra chia hết là n % k == 0. Số 0 chia hết cho mọi kk khác 0, vì 0 % k bằng 0; do đó 0 là số chẵn.

Với số âm, C++ cắt thương về phía 0, và số dư mang dấu của số bị chia. Chương trình sau in thương, số dư và kết luận “chia hết cho 4” của n=−6n = -6.

C++
#include <bits/stdc++.h>
using namespace std;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);

    int n;
    cin >> n;
    cout << n / 4 << ' ' << n % 4 << '\n';
    if (n % 4 == 0) {
        cout << "YES\n";
    } else {
        cout << "NO\n";
    }
}
-6

Vì −6=4⋅(−1)+(−2)-6 = 4 \cdot (-1) + (-2), nên -6 % 4 là -2, không phải 2. Hệ quả thực tế: so số dư với 0 luôn an toàn, còn so với một số dương thì không. Điều kiện n % 2 == 1 bỏ sót mọi số lẻ âm, vì -3 % 2 bằng -1. Muốn biết nn lẻ, hãy hỏi n % 2 != 0.

Hỏi nhanh

Điều kiện nào nhận ra đúng mọi số nguyên lẻ nn, kể cả số âm?

Ví dụChẵn hay lẻ

Đề. Cho số nguyên nn (−1000≤n≤1000-1000 \le n \le 1000). In EVEN nếu nn chẵn, ngược lại in ODD.

Lời giải. nn chẵn khi chia hết cho 2, tức n % 2 == 0. Điều kiện so với 0 nên đúng cả với số âm: -4 % 2 là 0, ra EVEN như test ví dụ, còn -3 % 2 là −1-1, khác 0, ra ODD. Viết ngược thành if (n % 2 == 1) cho nhánh ODD thì mọi số lẻ âm bị in nhầm EVEN.

Code đầy đủ
#include <bits/stdc++.h>
using namespace std;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);

    int n;
    cin >> n;
    if (n % 2 == 0) {
        cout << "EVEN\n";
    } else {
        cout << "ODD\n";
    }
}
-4

Ghép điều kiện: &&, || và !

Điều kiện “điểm hợp lệ” trong toán học là 0≤d≤100 \le d \le 10. C++ không đọc được dạng ba vế này; ta tách thành hai phép so sánh rồi nối bằng phép logic (logical operator).

  • A && B (và) đúng khi cả hai vế đều đúng.
  • A || B (hoặc) đúng khi ít nhất một vế đúng; gõ hai lần phím gạch đứng.
  • !A (không) đúng khi A sai.
C++
#include <bits/stdc++.h>
using namespace std;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);

    int d;
    cin >> d;
    if (0 <= d && d <= 10) {
        cout << "VALID\n";
    } else {
        cout << "INVALID\n";
    }
}
15

Nếu viết 0 <= d <= 10, trình biên dịch không báo lỗi. C++ tính từ trái sang: 0 <= d cho ra 0 hoặc 1, rồi so số 0 hoặc 1 đó với 10. Mà 0 hay 1 luôn không vượt quá 10, nên điều kiện đúng với mọi dd, kể cả 15 và −5-5.

Hỏi nhanh

Với d=15d = 15, đoạn if (0 <= d <= 10) { cout << "OK"; } else { cout << "BAD"; } in ra gì?

Hai lỗi cùng họ. x == 1 || 2 không có nghĩa “x bằng 1 hoặc 2”: vế 2 đứng một mình là số khác 0, tức luôn đúng; phải viết x == 1 || x == 2. Và a == b == c không kiểm ba số bằng nhau mà là (a == b) == c: với a=2a = 2, b=2b = 2, c=1c = 1 nó vẫn cho đúng, vì (2 == 2) là 1 và 1 == 1. Phải viết a == b && b == c.

Điều kiện đối lập, “điểm không hợp lệ”, là d < 0 || d > 10. Quy tắc phủ định cả điều kiện (luật De Morgan): đổi && thành || và ngược lại, đồng thời phủ định từng vế, < thành >=, <= thành >, == thành !=. Vậy !(0 <= d && d <= 10) bằng d < 0 || d > 10.

Thứ tự tính. ! được tính trước, rồi tới phép số học, rồi các phép so sánh, rồi &&, cuối cùng ||. Vì vậy a || b && c hiểu là a || (b && c), còn !x == 5 hiểu là (!x) == 5 chứ không phải !(x == 5). Khi trộn && với ||, luôn thêm ngoặc tròn dù không bắt buộc.

Dừng sớm (short-circuit). && và || tính từ trái sang và dừng ngay khi kết quả đã rõ: A && B mà A sai thì B bị bỏ qua; A || B mà A đúng thì B bị bỏ qua. Nhờ vậy ta đặt được một “lính gác” bên trái, chẳng hạn b != 0 && a % b == 0: với b=0b = 0 vế trái sai nên a % b không được tính. Đảo hai vế thì a % 0 chạy trước và chương trình thường dừng với lỗi chạy (runtime error).

Lính gác với b = 0: vế trái sai nên vế phải bị gạch, không được tính. Thử đảo hai vế, hay gõ 0 <= 15 <= 10.

Ví dụNăm nhuận

Đề. Năm yy (1≤y≤99991 \le y \le 9999) là năm nhuận nếu chia hết cho 400, hoặc chia hết cho 4 nhưng không chia hết cho 100. In YES nếu yy nhuận, ngược lại in NO.

Lời giải. Dịch từng cụm của đề sang C++. “Chia hết cho 400” là y % 400 == 0. “Chia hết cho 4 nhưng không chia hết cho 100” là y % 4 == 0 && y % 100 != 0. Chữ “hoặc” là ||. && được tính trước || nên ngoặc quanh vế sau không bắt buộc, nhưng đáng viết để thấy rõ hai trường hợp.

Gõ một năm hoặc bấm một năm mẫu: điểm xanh là năm nhuận, đỏ là không.

Thử bốn năm đại diện. 2024 chia hết cho 4, không chia hết cho 100: vế sau đúng, nhuận. 1900: vế đầu sai; ở vế sau y % 4 == 0 đúng nhưng y % 100 != 0 sai, nên không nhuận. 2000: vế đầu đúng, nhuận. 2023 không chia hết cho 4, không nhuận.

Code đầy đủ
#include <bits/stdc++.h>
using namespace std;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);

    int y;
    cin >> y;
    if (y % 400 == 0 || (y % 4 == 0 && y % 100 != 0)) {
        cout << "YES\n";
    } else {
        cout << "NO\n";
    }
}
1900

Điều kiện trên số: chia nguyên, tràn số, số thực

Chia nguyên. Hai số nguyên chia nhau vẫn ra số nguyên: 9 / 2 bằng 4. Điều kiện “trung bình cộng của aa và bb lớn hơn 4” viết thành (a + b) / 2 > 4 sẽ sai khi a=4a = 4, b=5b = 5: 9 / 2 bằng 4, 4 > 4 sai, trong khi trung bình thật là 4,5. Có hai cách chữa. Nhân thay vì chia: a + b > 2 * 4, mọi thứ vẫn là số nguyên nên chính xác. Hoặc chia cho số thực: (a + b) / 2.0 > 4.

Hỏi nhanh

Cho a=4a = 4, b=5b = 5, trung bình cộng là 4,5, rõ ràng lớn hơn 4. Điều kiện nào dưới đây lại cho kết quả sai?

Chia nguyên làm tròn xuống, mà nhiều đề cần làm tròn lên: 31 phút gửi xe tính theo khoảng 30 phút là 2 khoảng, trong khi 31 / 30 bằng 1. Muốn chia xx cho kk rồi làm tròn lên (với x≥0x \ge 0, k≥1k \ge 1), viết (x + k - 1) / k. Cộng thêm k−1k - 1 thì phép chia nào còn dư sẽ được đẩy lên thêm 1, còn phép chia hết thì phần cộng thêm chưa đủ để tăng: (31 + 29) / 30 bằng 2, còn (30 + 29) / 30 vẫn bằng 1.

Tràn số (overflow). Kiểu int chứa tới khoảng 2,1⋅1092{,}1 \cdot 10^9. Đề cho giá trị tới 10910^9 thì tổng hai số còn vừa, nhưng tích có thể tới 101810^{18}, và mọi if dựa vào kết quả tràn đều sai theo. Dùng long long cho cả biến đọc vào lẫn biến trung gian.

C++
#include <bits/stdc++.h>
using namespace std;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);

    long long a, b;
    cin >> a >> b;
    if (a * b > 1000000000000) {
        cout << "BIG\n";
    } else {
        cout << "SMALL\n";
    }
}
1000000000 1000000000

Ngưỡng ở đây là 101210^{12}. Khai báo a, b là int thì tích tràn ngay trước khi đem so sánh; với input trên chương trình thường in SMALL, sai, dù phần còn lại giống hệt.

Số thực. double lưu số gần đúng, nên 0.1 + 0.2 == 0.3 cho kết quả sai. Với số thực, < và > dùng được; còn so bằng thì hỏi khoảng cách giữa hai số có nhỏ hơn một sai số rất bé không, ví dụ fabs(x - y) < 1e-9.

Ví dụGửi xe theo phút

Đề. Bãi xe thu 5 đơn vị tiền cho 60 phút đầu; sau đó, cứ bắt đầu một khoảng 30 phút mới thì thu thêm 2 đơn vị. Tổng phí không vượt quá 25. Xe gửi tt phút (1≤t≤14401 \le t \le 1440), in phí phải trả.

Lời giải. Phí khởi đầu là 5. Chỉ khi t>60t > 60 mới có phần thêm: còn x=t−60x = t - 60 phút, và “bắt đầu một khoảng mới” nghĩa là số khoảng bằng x/30x / 30 làm tròn lên, tức (x + 29) / 30. Với t=91t = 91: x=31x = 31, (31 + 29) / 30 bằng 2 khoảng, phí 5+2⋅2=95 + 2 \cdot 2 = 9. Cuối cùng chặn phí ở 25 bằng một if nữa. Hai if này viết rời nhau, vì cả hai cùng có thể chạy.

Thử các giá trị sát ngưỡng: t=60t = 60 ra 5, t=61t = 61 ra 7, t=90t = 90 vẫn 7, t=91t = 91 ra 9. Viết t >= 60 hay quên làm tròn lên đều lộ ra ở đúng những giá trị này.

Code đầy đủ
#include <bits/stdc++.h>
using namespace std;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);

    int t;
    cin >> t;
    int fee = 5;
    if (t > 60) {
        fee += (t - 60 + 29) / 30 * 2;
    }
    if (fee > 25) {
        fee = 25;
    }
    cout << fee << '\n';
}
91

Viết gọn: ?:, max, min và abs

Khi chỉ chọn giữa hai giá trị, if else có thể viết gọn bằng toán tử ba ngôi (ternary operator): điều kiện ? giá trị nếu đúng : giá trị nếu sai. Hàm max(a, b) cho số lớn hơn, min(a, b) cho số nhỏ hơn, abs(x) cho giá trị tuyệt đối, chẳng hạn abs(a - b) là khoảng cách giữa hai số trên trục số. Chương trình dưới tìm số lớn hơn của hai số bằng ba cách tương đương.

C++
#include <bits/stdc++.h>
using namespace std;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);

    int a, b;
    cin >> a >> b;
    int x;
    if (a > b) {
        x = a;
    } else {
        x = b;
    }
    int y = a > b ? a : b;
    int z = max(a, b);
    cout << x << ' ' << y << ' ' << z << '\n';
}
3 8

Có hai điều cần cẩn thận. Hai đối số của max và min phải cùng kiểu: max(x, y) với x là int và y là long long không biên dịch được, cách chữa là đổi x thành long long. Và khi in thẳng một biểu thức ?: bằng cout, bọc cả biểu thức trong ngoặc tròn: cout << (x < 0 ? "NEG" : "POS");. Bỏ ngoặc thì << được tính trước ?:, và chương trình hoặc báo lỗi biên dịch, hoặc chỉ in 0, 1.

Với ba số thì lồng hai lần, max(a, max(b, c)), hoặc viết max({a, b, c}).

Ví dụPhòng học chung

Đề. Nhóm thứ nhất dùng phòng từ phút aa đến trước phút bb, nhóm thứ hai từ phút cc đến trước phút dd (0≤a<b≤14400 \le a < b \le 1440, 0≤c<d≤14400 \le c < d \le 1440). In số phút chung của hai khoảng; chỉ chạm nhau tại một thời điểm thì là 0.

Lời giải. Phần chung bắt đầu khi cả hai nhóm đã vào, tức ở max⁡(a,c)\max(a, c), và kết thúc khi một nhóm đã ra, tức ở min⁡(b,d)\min(b, d). Độ dài là hiệu hai mốc đó. Nếu hai khoảng rời nhau hoặc chỉ chạm nhau thì hiệu này âm hoặc bằng 0, và đáp án là 0, nên in max(0, r - l). Với ví dụ: l=max⁡(60,90)=90l = \max(60, 90) = 90, r=min⁡(120,150)=120r = \min(120, 150) = 120, đáp án 30. Cách này không cần xét riêng khoảng nào nằm bên trái.

Code đầy đủ
#include <bits/stdc++.h>
using namespace std;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);

    int a, b, c, d;
    cin >> a >> b >> c >> d;
    int l = max(a, c), r = min(b, d);
    cout << max(0, r - l) << '\n';
}
60 120 90 150

Khi đi thi

  • Với mỗi ngưỡng trong đề, thử ba giá trị: sát dưới, đúng bằng và sát trên. Lỗi < thay <= chỉ lộ ở các giá trị đó, mà test ví dụ của đề thường không chứa chúng.
  • Trong chuỗi else if, mỗi nhánh thừa hưởng điều kiện “các nhánh trước đều sai”: xếp ngưỡng theo một chiều nhất quán và đừng viết lại cận đã biết; khi trộn && với ||, luôn thêm ngoặc tròn.
  • Chép nguyên văn từng chữ output của đề (YES, TRY_AGAIN, hoa thường, dấu gạch dưới), cho mọi biến được gán trong nhánh một giá trị ban đầu, và dùng long long khi dữ liệu cỡ 10910^9.

Tóm tắt

  • if (điều kiện) { ... } chạy khối khi điều kiện đúng; else nhận mọi trường hợp còn lại.
  • Chuỗi else if chạy đúng nhánh đầu tiên có điều kiện đúng, nên thứ tự nhánh là một phần của lời giải; else gắn với if gần nhất chưa có else.
  • = là gán, == là so sánh; if (x = 5) vẫn biên dịch nhưng sai nghĩa. Luôn dùng ngoặc nhọn và không đặt dấu chấm phẩy sau if (...).
  • Nối điều kiện bằng &&, ||, !; 0 <= d <= 10 luôn đúng, phải viết 0 <= d && d <= 10; && tính trước || nên cần ngoặc khi trộn.
  • Số dư mang dấu của số bị chia, nên số lẻ là n % 2 != 0; / giữa hai số nguyên cắt phần thập phân, làm tròn lên thì viết (x + k - 1) / k.
  • max, min, abs và ?: thay cho những if else chỉ chọn giữa hai giá trị.

Bài tập

Đọc thêm