Basic·Bài B1

cin and cout

Mỗi bài trên trình chấm có chung một dáng: chương trình đọc dữ liệu từ đầu vào chuẩn (standard input), tính toán, rồi in kết quả ra đầu ra chuẩn (standard output). Trình chấm không xem chương trình nghĩ gì: nó đưa vào một đoạn chữ, nhận lại một đoạn chữ rồi so với đáp án. Đọc nhầm thứ tự hai số, in dính hai số vào nhau, hay để kết quả vượt khỏi int đều dẫn tới Sai (Wrong Answer), dù phần tính toán đúng.

Ở bài B0 chương trình chỉ in những thứ có sẵn. Bài này cho nó đọc dữ liệu bằng cin, rồi đi qua những chỗ người mới hay vấp: đọc sai kiểu, đọc chữ, thương và phần dư của số vừa đọc, số thực và số vượt khỏi int.

cin: đọc từng mẩu từ dải chữ

Trình chấm đưa dữ liệu vào chương trình như một dải chữ liền nhau, giống như có người gõ bàn phím mà không ai nhìn màn hình. cin là luồng đầu vào chuẩn, và cin >> x lấy một giá trị từ dải chữ đó bỏ vào biến x. Mũi tên chỉ hướng đi của dữ liệu: cin >> x đi từ cin vào x, ngược với cout << x đi từ x ra cout. Viết ngược thành cin << x hay cout >> x là lỗi biên dịch.

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

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

    int a, b, c;
    cin >> a >> b >> c;
    cout << a * b + c << '\n';
}
12 7
30

'\n' (nháy đơn, một ký tự) làm đúng việc của "\n" ở B0, và từ đây ta viết nháy đơn. Gõ nhầm thành '/n' thì g++ chỉ cảnh báo, rồi chương trình in ra một số lạ thay vì xuống dòng.

cin nhớ nó đã đọc tới đâu. Mỗi lần >> bỏ qua mọi khoảng trắng đứng trước (dấu cách, xuống dòng, tab), rồi lấy ra một mẩu chữ liền nhau và đổi thành giá trị theo kiểu của biến. Ở trên a nhận 12, b nhận 7, c nhận 30 dù nó nằm ở dòng khác, và kết quả là 12⋅7+30=11412 \cdot 7 + 30 = 114.

Ô mờ là phần đã đọc hoặc khoảng trắng bị bỏ qua, ô cam là mẩu chữ vừa vào biến. Thử đổi input thành 12abc hay đổi kiểu biến.

Hai hệ quả dùng hằng ngày. Dữ liệu nằm trên một dòng hay nhiều dòng, câu lệnh đọc vẫn y hệt; chỉ thứ tự là quan trọng. Và thứ tự đọc là thứ tự trong đề: đề ghi “một dòng chứa nn và kk” thì đọc cin >> n >> k. Đọc ngược là hoán đổi giá trị hai biến, và bài sai dù mọi phép tính phía sau đều đúng.

Chương trình không có người ngồi trước màn hình, nên đừng in lời nhắc kiểu Nhap a:: mọi chữ thừa trong output đều bị tính là sai. Cũng vì thế, in đúng khuôn của đề: “trên cùng một dòng” thì chèn ' ' giữa các giá trị, “mỗi giá trị một dòng” thì chèn '\n'.

Hỏi nhanh

Dữ liệu vào gồm hai dòng: dòng đầu là 12, dòng sau là 5. Chương trình có int a, b; cin >> b >> a; cout << a - b << '\n';. Nó in ra gì?

Khi chạy thử trên máy mình, cin đọc từ bàn phím, và chương trình đứng yên không phải là treo: nó đang chờ bạn gõ số rồi nhấn Enter. Muốn khỏi gõ lại mỗi lần, ghi dữ liệu mẫu vào tệp input.txt rồi chạy ./main < input.txt (Linux, macOS) hoặc main.exe < input.txt (Command Prompt của Windows; PowerShell không nhận dấu <). Dấu < đưa nội dung tệp vào làm đầu vào chuẩn, đúng như trình chấm làm.

Đến đây ta hiểu được hai dòng đầu main của khung. Mặc định cin và cout làm thêm hai việc để phục vụ người ngồi gõ phím: giữ đúng thứ tự khi chương trình dùng lẫn lệnh đọc ghi kiểu cũ của ngôn ngữ C (scanf, printf), và in hết chữ đang chờ ra màn hình trước mỗi lần đọc để lời nhắc kịp hiện lên. Trình chấm không cần hai việc đó, mà chúng làm chậm: ios::sync_with_stdio(false); tắt việc thứ nhất, cin.tie(0); tắt việc thứ hai. Đề thi viết số lớn bằng lũy thừa của 10: 10610^6 là số 1 theo sau 6 chữ số 0, tức một triệu, còn 10910^9 là một tỷ. Đọc 10610^6 số mà thiếu hai dòng này thì chương trình có thể chậm hơn vài lần, đủ để vượt giới hạn một giây. Đã có dòng thứ nhất thì không dùng lẫn cin, cout với scanf, printf nữa.

Cùng lý do đó, endl in xuống dòng và xả bộ đệm ngay, nên in 10610^6 dòng bằng endl có thể chậm hơn '\n' hàng chục lần. Ngoại lệ duy nhất là bài tương tác (bài 28), nơi phải xả sau mỗi câu hỏi.

Một số kỳ thi đọc ghi qua tệp, tên tệp ghi trong đề. Khi đó thêm hai dòng freopen ở đầu main; chúng nối stdin, stdout vào tệp, còn cin, cout giữ nguyên:

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

int main() {
    freopen("bai.inp", "r", stdin);
    freopen("bai.out", "w", stdout);
    ios::sync_with_stdio(false);
    cin.tie(0);

    int a, b;
    cin >> a >> b;
    cout << a + b << '\n';
}

Chỉ thêm khi đề yêu cầu: gửi lên LQDOJ mà còn freopen thì chương trình đi tìm tệp không tồn tại và không đọc được dữ liệu của trình chấm.

Ví dụHai số đảo chỗ

Đề. Một dòng chứa hai số nguyên aa và bb (−1000≤a,b≤1000-1000 \le a, b \le 1000). In bb rồi đến aa trên cùng một dòng.

Lời giải. Đọc đúng thứ tự đề cho, cin >> a >> b, rồi đảo thứ tự lúc in; không cần đổi giá trị hai biến. Giữa hai số phải có ' ', thiếu nó thì máy in 83 thay vì 8 3. Số âm không cần xử lý gì thêm: cin đọc cả dấu -.

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

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

    int a, b;
    cin >> a >> b;
    cout << b << ' ' << a << '\n';
}
3 8

Kiểu của biến: số, ký tự, xâu

Kiểu của biến quyết định cin lấy bao nhiêu chữ. Với biến số, cin không lấy trọn mẩu chữ mà chỉ lấy phần đầu còn đúng dạng số của kiểu đó (có thể mở đầu bằng - hoặc +), và dừng ở ký tự đầu tiên không hợp. Đọc 3.5 vào biến int chỉ lấy được 3; phần .5 nằm lại chờ lần đọc sau. Lần đọc sau gặp dấu chấm, không thể bắt đầu một số nguyên, nên cin rơi vào trạng thái lỗi: biến đang đọc nhận 0, và mọi lệnh cin về sau không làm gì nữa.

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

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

    int a = -5, b = -5, c = -5;
    cin >> a >> b >> c;
    cout << a << ' ' << b << ' ' << c << '\n';
}
3.5 7 8

Ba biến được gán sẵn −5-5 để thấy biến nào không được đọc. a lấy 3, b hỏng ở chỗ .5 nên nhận 0, còn c giữ nguyên −5-5: số 7 và 8 không bao giờ được đọc. Số quá lớn so với kiểu cũng làm cin hỏng: đọc 3000000000 vào int cho 2147483647 rồi cũng vào trạng thái lỗi. Nghĩa là đề cho giá trị tới 101010^{10} mà khai báo int thì chương trình sai ngay lúc đọc, trước cả khi có phép tính nào tràn. Thấy những số 0 hay 2147483647 không ngờ tới, hãy in lại các biến ngay sau lệnh cin để xem dữ liệu có vào đúng không.

Hỏi nhanh

Dữ liệu vào là một dòng 3.5 7. Chương trình có int a, b; cin >> a >> b; rồi in a và b cách nhau một dấu cách. Nó in ra gì?

Với chữ, >> vẫn bỏ qua khoảng trắng rồi lấy một mẩu. Biến string (xâu) nhận cả mẩu liền nhau, còn biến char chỉ nhận đúng một ký tự không phải khoảng trắng.

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

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

    string a, b;
    char c;
    cin >> a >> b >> c;
    cout << a << '|' << b << '|' << c << '\n';
}
Nguyen Van An

Ba biến nhận Nguyen, Van và A; chữ n cuối còn nằm đó chưa ai đọc. Vì >> dừng ở khoảng trắng, nó không đọc nổi một dòng có dấu cách bên trong như họ tên hay một câu văn. Để đọc trọn một dòng ta dùng getline(cin, s): nó lấy mọi ký tự tới hết dòng, kể cả dấu cách, và bỏ dấu xuống dòng đi.

Chỗ hay vấp là getline đứng ngay sau một lệnh >>. Lệnh >> đọc xong số thì dừng sát sau số, nên ký tự xuống dòng vẫn nằm trong dải chữ, và getline đọc được ngay một dòng rỗng. Chèn cin.ignore(); vào giữa để bỏ ký tự xuống dòng đó:

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

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

    int n;
    string name;
    cin >> n;
    cin.ignore();
    getline(cin, name);
    cout << n << ": " << name << '\n';
}
3
Nguyen Van An

Xóa dòng cin.ignore(); thì chương trình chỉ in 3:, vì name nhận dòng rỗng. ignore() chỉ bỏ đúng một ký tự, nên nếu sau số còn dấu cách thừa, hay dòng kết thúc bằng cặp \r\n của Windows, thì nó bỏ chưa đủ. Khi đó viết cin.ignore(1000, '\n');: bỏ đi tới hết dòng chứa số (tối đa 1000 ký tự, thừa sức cho một dòng như vậy).

Ví dụGhép hai chữ số

Đề. Một dòng chứa chữ số hàng chục aa (1≤a≤91 \le a \le 9) và chữ số hàng đơn vị bb (0≤b≤90 \le b \le 9). In số có hai chữ số này.

Lời giải. Số cần in, viết ra giấy, chính là chữ số aa đứng liền trước chữ số bb. Đọc hai chữ số vào hai biến char: mỗi >> bỏ dấu cách rồi lấy đúng một ký tự. In hai ký tự liền nhau, không chèn gì, là ra số cần tìm; vì a≥1a \ge 1 nên không có chữ số 0 thừa ở đầu. Cách số học là đọc vào int rồi in 10 * a + b, đúng kiểu ghép của mục sau.

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

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

    char a, b;
    cin >> a >> b;
    cout << a << b << '\n';
}
4 2

Thương và phần dư: tách, ghép và quay vòng

B0 đã cho biết / giữa hai số nguyên là thương và % là phần dư. Với dữ liệu đọc vào, hai phép này là bộ công cụ chính của các bài nhập môn, vì chúng chia một số thành các phần. Đây chính là phép chia có dư: số bị chia bằng thương nhân số chia cộng số dư, và số dư luôn nhỏ hơn số chia. Viết bằng C++, với n≥0n \ge 0 và k≥1k \ge 1:

n=(n/k)⋅k+n%k,0≤n%k<k.n = (n / k) \cdot k + n \% k, \qquad 0 \le n \% k < k.

Ba cách dùng thường gặp:

  • Đổi đơn vị. Đổi mm phút ra giờ và phút: m / 60 là số giờ trọn, m % 60 là số phút lẻ. Chẳng hạn 200=3⋅60+20200 = 3 \cdot 60 + 20 cho 3 giờ 20 phút.
  • Tách và ghép chữ số. Trong hệ thập phân, n % 10 là chữ số cuối và n / 10 là phần còn lại sau khi bỏ chữ số cuối. Ngược lại, 10 * n + d gắn thêm chữ số dd vào cuối nn.
  • Quay vòng. Đồng hồ chỉ 22 giờ, 30 giờ nữa là mấy giờ? Cộng thẳng ra 52, nhưng một ngày chỉ có 24 giờ: 52 % 24 = 4 là giờ trên đồng hồ, còn 52 / 24 = 2 là số vòng đã đi trọn.

Với số âm, % cho phần dư âm (-7 % 5 ra -2); khi cần phần dư nằm trong [0,k)[0, k), viết (x % k + k) % k.

Ví dụTổng ba chữ số

Đề. Cho số nguyên nn có đúng ba chữ số (100≤n≤999100 \le n \le 999). In tổng ba chữ số của nn.

Lời giải. Mỗi chữ số lấy ra bằng một biểu thức. Chia cho 10 thì số dư là chữ số hàng đơn vị: n % 10. Thương n / 10 là nn bỏ đi chữ số cuối, nên chữ số cuối của thương, n / 10 % 10, là chữ số hàng chục. Chia cho 100 bỏ đi hai chữ số cuối, còn lại chữ số hàng trăm: n / 100. Biểu thức n / 10 % 10 tính từ trái sang phải, tức (n / 10) % 10.

Kéo n: mỗi biểu thức lấy đúng một chữ số.

Với n=307n = 307: 307 / 100 = 3, 307 / 10 = 30 nên 307 / 10 % 10 = 0, 307 % 10 = 7, tổng là 10. Số nhiều chữ số hơn thì lặp lại cặp % 10, / 10 bằng vòng lặp, việc của bài B3.

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

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

    int n;
    cin >> n;
    cout << n / 100 + n / 10 % 10 + n % 10 << '\n';
}
307

Ví dụVị trí trên vòng

Đề. Một vòng tròn có kk vị trí đánh số từ 00 đến k−1k - 1; sau vị trí k−1k - 1 là vị trí 00. Bắt đầu ở vị trí pp, đi ss bước theo chiều tăng số. In vị trí cuối cùng. Giới hạn: 1≤k≤1001 \le k \le 100, 0≤p<k0 \le p < k, 0≤s≤10000 \le s \le 1000.

Lời giải. Nếu vòng được duỗi thẳng thì ta dừng ở p+sp + s. Mỗi kk bước là đi trọn một vòng và quay về chỗ cũ, nên chỉ phần dư của p+sp + s khi chia cho kk quyết định vị trí: đáp án là (p + s) % k. Với ví dụ k=7k = 7, p=5p = 5, s=4s = 4: 5+4=9=1⋅7+25 + 4 = 9 = 1 \cdot 7 + 2, dừng ở vị trí 2. Đây đúng là bài đồng hồ ở trên với k=24k = 24.

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

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

    int k, p, s;
    cin >> k >> p >> s;
    cout << (p + s) % k << '\n';
}
7 5 4

Số thực và số lớn: double, long long

Khi dữ liệu hay kết quả có phần thập phân ta dùng double; cin đọc và cout in nó như số nguyên. Mặc định cout in số thực với 6 chữ số có nghĩa, và số lớn bị đổi sang dạng khoa học. Muốn đúng kk chữ số sau dấu phẩy, thêm fixed << setprecision(k) trước giá trị.

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

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

    double x;
    cin >> x;
    cout << x << '\n';
    cout << fixed << setprecision(2) << x << '\n';
}
1234567.891

fixed << setprecision(k) làm tròn chứ không cắt, và ở lại với cout tới hết chương trình; nó chỉ ảnh hưởng số thực. double chỉ lưu gần đúng, được chừng 15 đến 16 chữ số có nghĩa, nên số nguyên lớn thì giữ bằng long long chứ đừng đưa vào double.

Chỗ sai hay gặp nhất với double là phép chia. Kiểu của một phép tính do các toán hạng quyết định, không do biến nhận kết quả: a / b với hai int là chia nguyên, tính xong rồi mới đổi sang double, lúc đó phần lẻ đã mất.

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

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

    int a = 7, b = 2;
    double x = a / b;
    double y = 1.0 * a / b;
    cout << x << ' ' << y << '\n';
}

x nhận 7 / 2 = 3 rồi mới thành 3.0. Ở y, 1.0 * a ra 7.0 trước, nên phép chia sau đó là chia thực; viết (double)a / b cũng được.

Hỏi nhanh

Cho int a = 7, b = 2;. Lệnh cout << (a + b) / 2 * 1.0 << ' ' << 1.0 * (a + b) / 2; in ra gì?

Số nguyên cũng có giới hạn. int chỉ chứa tới 2147483647 (C++ đặt sẵn tên INT_MAX), còn long long tới khoảng 9,2⋅10189{,}2 \cdot 10^{18} (LLONG_MAX, số có 19 chữ số). Khi kết quả một phép tính vượt phạm vi của kiểu, số bị tràn (overflow). Tràn số thì C++ không báo lỗi, cũng không hứa kết quả nào: thường là một số sai, nhiều khi là số âm. Đây là lý do một bài đúng thuật toán vẫn trượt vài test lớn.

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

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

    cout << INT_MAX << ' ' << LLONG_MAX << '\n';
    int a = 100000, b = 100000;
    cout << 1LL * a * b << '\n';
}
Mỗi vạch gấp 1000 lần vạch trước. Đổi a, b để thấy lúc nào tích vượt int.

Trước khi gõ code, dùng giới hạn của đề để ước lượng giá trị lớn nhất của dữ liệu, của từng phép tính trung gian và của đáp án. Hai số 10510^5 nhân nhau cho 101010^{10}; ba số 10910^9 cộng nhau cho 3⋅1093 \cdot 10^9; tổng 1+2+⋯+n1 + 2 + \dots + n với n=105n = 10^5 cỡ 5⋅1095 \cdot 10^9. Cả ba đều vượt int dù từng số đọc vào vẫn vừa int. Cái gì có thể vượt khoảng 2⋅1092 \cdot 10^9 thì khai báo long long ngay từ đầu; nghi ngờ thì cứ chọn nó. Biến long long đọc bằng cin và in bằng cout y hệt int.

Quy tắc toán hạng ở trên áp dụng cả ở đây: với a, b là int, câu lệnh long long c = a * b; vẫn sai, vì tích đã tính xong bằng int (và tràn) rồi mới được gán vào c. Cách chữa là hằng 1LL, số 1 kiểu long long, đặt ở đầu chuỗi nhân như trong chương trình trên: phép nhân đầu tiên đã chạy bằng long long, và các phép sau cũng vậy vì chuỗi nhân tính từ trái sang phải. Gọn nhất vẫn là khai báo a, b là long long từ đầu.

Hỏi nhanh

Đã đọc int n; cin >> n; với 0≤n≤1050 \le n \le 10^5 và cần in 1+2+⋯+n1 + 2 + \dots + n, tính bằng công thức n(n+1)/2n(n+1)/2. Đáp án lớn nhất khoảng 5⋅1095 \cdot 10^9. Dòng nào cho kết quả đúng với mọi nn trong giới hạn?

Ví dụTích lớn

Đề. Cho hai số nguyên không âm a,b≤109a, b \le 10^9. In tích a×ba \times b.

Lời giải. Mỗi số còn vừa int, nhưng tích lớn nhất là 101810^{18}: vượt xa 2,1⋅1092{,}1 \cdot 10^9 và vẫn nhỏ hơn 9,2⋅10189{,}2 \cdot 10^{18}, tức vừa long long. Khai báo a, b là long long, đọc, nhân, in. Để int cả hai và nhân thẳng thì ngay test ví dụ 105⋅10510^5 \cdot 10^5 đã in ra một con số sai mà không có cảnh báo nào.

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

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

    long long a, b;
    cin >> a >> b;
    cout << a * b << '\n';
}
100000 100000

Khi đi thi

  • Đọc kỹ định dạng vào và ra trước khi gõ: thứ tự các biến, dấu cách, xuống dòng, đọc ghi bàn phím hay tệp. Không in lời nhắc; chạy ví dụ của đề và so từng ký tự.
  • Trước khi khai báo, ước lượng giá trị lớn nhất của dữ liệu, của các phép tính trung gian và của đáp án; có thể vượt 2⋅1092 \cdot 10^9 thì dùng long long ngay từ đầu, kể cả cho biến đọc vào.
  • Kết quả lạ thì in lại các biến vừa đọc, rồi soát theo thứ tự: đọc sai kiểu hoặc quá phạm vi (số 0 hay 2147483647), phép chia nguyên đang cắt phần thập phân, phép tính trung gian tràn int.

Tóm tắt

  • cin >> a >> b; bỏ qua dấu cách và xuống dòng, đọc theo đúng thứ tự trong đề; không in lời nhắc. Đầu main có ios::sync_with_stdio(false); cin.tie(0);, và xuống dòng bằng '\n' thay cho endl.
  • Với biến số, cin chỉ lấy phần đầu đúng dạng số. Đọc sai kiểu hay quá phạm vi làm cin hỏng: biến nhận 0 hoặc INT_MAX, các lệnh đọc sau bị bỏ qua.
  • char nhận một ký tự, string nhận một từ, getline nhận cả dòng; giữa >> và getline phải bỏ phần còn lại của dòng trước.
  • Phép chia có dư: n = (n / k) * k + n % k, số dư nhỏ hơn số chia. / và % dùng để đổi đơn vị, tách ghép chữ số, quay vòng.
  • int chứa tới 2,1⋅1092{,}1 \cdot 10^9, long long tới 9,2⋅10189{,}2 \cdot 10^{18}; kiểu phép tính do toán hạng quyết định nên viết 1LL * a * b và 1.0 * a / b.

Bài tập

Đọc thêm