Đồng dư và phép toán modulo
Rất nhiều đề thi có câu: “in ra kết quả sau khi chia lấy dư cho 109 + 7”. Lý do là đáp số thật quá lớn (ví dụ 1000! có 2568 chữ số), không biến nào lưu được. Bài này giúp các bạn tính đúng số dư đó mà không bao giờ phải tính ra con số khổng lồ.
Các bạn nên học trước bài Phép chia có dư và chia hết.
Đồng dư là gì?
Phần tiêu đề “Đồng dư là gì?”Hai số đồng dư theo modulo m nếu chúng có cùng số dư khi chia cho m. Viết:
a ≡ b (mod m)
Ví dụ 17 ≡ 2 (mod 5) vì 17 và 2 cùng chia 5 dư 2. Ngày thứ 100 và ngày thứ 2 tính từ một thứ Hai có cùng thứ vì 100 ≡ 2 (mod 7).
Cách nói khác: a ≡ b (mod m) khi a - b chia hết cho m.
Ba quy tắc quan trọng
Phần tiêu đề “Ba quy tắc quan trọng”Muốn biết số dư của một tổng, hiệu hay tích, chỉ cần số dư của từng số:
| Phép toán | Quy tắc |
|---|---|
| Cộng | (a + b) % m = (a % m + b % m) % m |
| Nhân | (a × b) % m = ((a % m) × (b % m)) % m |
| Trừ | (a - b) % m = (a % m - b % m + m) % m |
Vì sao đúng? Lấy m = 10, số dư chính là chữ số hàng đơn vị. Muốn biết chữ số tận cùng của 123 × 456, các bạn chỉ cần nhân 3 × 6 = 18, được chữ số 8. Các phần còn lại (120 × 456, 3 × 450) đều tận cùng bằng 0, không ảnh hưởng. Với m bất kỳ cũng vậy: phần chia hết cho m không làm thay đổi số dư.
Ở phép trừ, cộng thêm m là để kết quả không bị âm (xem số dư của số âm).
Lưu ý: không có quy tắc như vậy cho phép chia. Ví dụ (12 / 4) % 5 = 3, nhưng (12 % 5) / (4 % 5) = 2 / 4, không phải số nguyên. Chia theo modulo cần một công cụ khác (nghịch đảo modulo) sẽ học ở bài Lũy thừa nhanh.
Lấy dư sau mỗi bước
Phần tiêu đề “Lấy dư sau mỗi bước”Tính n! % (109 + 7): nhân dần từ 1 đến n, sau mỗi lần nhân thì lấy dư ngay. Theo quy tắc nhân, kết quả cuối cùng vẫn đúng, mà các số trung gian luôn nhỏ hơn 109 + 7.
MOD = 10 ** 9 + 7n = int(input())gt = 1for i in range(1, n + 1): gt = gt * i % MOD # lấy dư ngay sau mỗi lần nhânprint(gt) # n = 1000000 in ra 641102369#include <iostream>using namespace std;
const long long MOD = 1000000007;
int main() { int n; cin >> n; long long gt = 1; for (int i = 1; i <= n; i++) { gt = gt * i % MOD; // lấy dư ngay sau mỗi lần nhân } cout << gt << endl; // n = 1000000 in ra 641102369 return 0;}Vì sao C++ không bị tràn? gt luôn nhỏ hơn MOD (khoảng 109), i không quá
106, nên gt * i không quá 1015, vừa long long (tới khoảng
9,2 × 1018). Nhân hai số cùng nhỏ hơn MOD thì tích nhỏ hơn 1018, vẫn
vừa.
Lưu ý:
- Trong C++ phải dùng
long long. Viếtint gtthìgt * ibị tràn ngay. - Nếu hai số cần nhân chưa được lấy dư (ví dụ đều khoảng 1018), phải lấy
dư từng số trước:
(a % MOD) * (b % MOD) % MOD. - Python không bị tràn số nhưng vẫn nên lấy dư sau mỗi bước, nếu không các số phình to và chương trình chạy rất chậm.
Vì sao lại là 109 + 7? Số này đủ lớn để đáp số ít bị “trùng”, đủ nhỏ để
tích hai số dư vừa long long, và là số nguyên tố, một tính chất cần cho phép chia
theo modulo.
Số dư của số rất lớn
Phần tiêu đề “Số dư của số rất lớn”Số có 105 chữ số phải đọc dưới dạng xâu. Đọc từ trái sang phải, mỗi chữ số mới làm số cũ nhân 10 rồi cộng chữ số đó (1234 = 123 × 10 + 4). Áp dụng quy tắc cộng và nhân, chỉ cần giữ số dư:
số dư mới = (số dư cũ × 10 + chữ số) % m
Chạy tay 1234 % 7:
| Chữ số | Phép tính | Số dư |
|---|---|---|
| 1 | (0 × 10 + 1) % 7 | 1 |
| 2 | (1 × 10 + 2) % 7 | 5 |
| 3 | (5 × 10 + 3) % 7 | 4 |
| 4 | (4 × 10 + 4) % 7 | 2 |
1234 = 7 × 176 + 2. Đúng.
Đếm theo số dư
Phần tiêu đề “Đếm theo số dư”Nhiều bài hỏi “có bao nhiêu cặp có tổng chia hết cho k”. Thay vì thử mọi cặp, ta nhóm các số theo số dư khi chia cho k:
- Hai số có tổng chia hết cho k khi tổng hai số dư bằng 0 hoặc bằng k.
- Hai số có hiệu chia hết cho k khi chúng cùng số dư (đồng dư).
Ý thứ hai rất mạnh khi kết hợp với tổng tiền tố: một đoạn liên tiếp có tổng chia hết cho k khi hai tổng tiền tố ở hai đầu đoạn có cùng số dư (bài 13).
Luyện tập
Phần tiêu đề “Luyện tập”Gồm 21 bài, chia thành 4 nhóm từ dễ đến khó. Mỗi bài có một dữ liệu kiểm tra: các bạn chạy chương trình của mình với dữ liệu đó rồi so với đáp số.
Nhóm 1. Cộng, trừ, nhân theo modulo
Phần tiêu đề “Nhóm 1. Cộng, trừ, nhân theo modulo”Bài 1. Tích hai số lớn
Phần tiêu đề “Bài 1. Tích hai số lớn”Nhập ba số nguyên dương a, b, m (a, b ≤ 1018, m ≤ 109). In ra (a × b) % m.
Ví dụ: nhập 123456789 987654321 1000 thì in ra 269.
Kiểm tra: nhập 1000000000000000000 999999999999999999 1000000007.
Bài 2. Giai thừa modulo
Phần tiêu đề “Bài 2. Giai thừa modulo”Nhập số nguyên dương n (n ≤ 106). In ra n! % (109 + 7).
Ví dụ: nhập 10 thì in ra 3628800.
Kiểm tra: nhập 1000000.
Bài 3. Tổng từ 1 đến n
Phần tiêu đề “Bài 3. Tổng từ 1 đến n”Nhập hai số nguyên dương n, m (n ≤ 1018, m ≤ 109). In ra (1 + 2 + … + n) % m.
Ví dụ: nhập 100 7 thì in ra 3.
Kiểm tra: nhập 1000000000000000000 1000000007.
Bài 4. Tổng bình phương
Phần tiêu đề “Bài 4. Tổng bình phương”Nhập số nguyên dương n (n ≤ 106). In ra (12 + 22 + … + n2) % (109 + 7).
Ví dụ: nhập 10 thì in ra 385.
Kiểm tra: nhập 1000000.
Bài 5. Hiệu theo modulo
Phần tiêu đề “Bài 5. Hiệu theo modulo”Nhập ba số nguyên dương a, b, m (a, b ≤ 1018, m ≤ 109). In ra số dư không âm của a - b khi chia cho m.
Ví dụ: nhập 5 12 7 thì in ra 0.
Kiểm tra: nhập 3 1000000000000000000 1000000007.
Bài 6. Số Fibonacci modulo
Phần tiêu đề “Bài 6. Số Fibonacci modulo”Dãy Fibonacci: F(0) = 0, F(1) = 1, F(n) = F(n - 1) + F(n - 2). Nhập n (n ≤ 106), in ra F(n) % (109 + 7).
Ví dụ: nhập 10 thì in ra 55.
Kiểm tra: nhập 1000000.
Nhóm 2. Số rất lớn
Phần tiêu đề “Nhóm 2. Số rất lớn”Bài 7. Số dư của số rất lớn
Phần tiêu đề “Bài 7. Số dư của số rất lớn”Dòng đầu là số nguyên dương n có không quá 105 chữ số. Dòng thứ hai là số nguyên dương m (m ≤ 109). In ra n % m.
Ví dụ:
| Nhập | In ra |
|---|---|
20267 | 3 |
Kiểm tra: nhập
1234567891234567891234567891000000009Bài 8. Hai số rất lớn đồng dư
Phần tiêu đề “Bài 8. Hai số rất lớn đồng dư”Ba dòng lần lượt là hai số nguyên dương x, y (mỗi số không quá 105 chữ số) và số nguyên dương m (m ≤ 109). In ra YES nếu x ≡ y (mod m), ngược lại in ra NO.
Ví dụ:
| Nhập | In ra |
|---|---|
202657 | NO |
Kiểm tra: nhập
987654321098765432101234567890123456789037Bài 9. Chia hết cho 11
Phần tiêu đề “Bài 9. Chia hết cho 11”Nhập số nguyên dương n có không quá 105 chữ số. In ra YES nếu n chia hết cho 11, ngược lại in ra NO.
Ví dụ: nhập 121 thì in ra YES.
Kiểm tra: nhập 918273645546372819.
Bài 10. Ghép các số từ 1 đến n
Phần tiêu đề “Bài 10. Ghép các số từ 1 đến n”Viết liền các số 1, 2, 3, …, n thành một số rất lớn (ví dụ n = 12 được 123456789101112). Nhập n (n ≤ 106), in ra số đó chia lấy dư cho 109 + 7.
Ví dụ: nhập 10 thì in ra 345678826.
Kiểm tra: nhập 1000.
Bài 11. Số toàn chữ số 1
Phần tiêu đề “Bài 11. Số toàn chữ số 1”Nhập hai số nguyên dương n, m (n ≤ 106, m ≤ 109). In ra số gồm n chữ số 1 (111…1) chia lấy dư cho m.
Ví dụ: nhập 10 7 thì in ra 5.
Kiểm tra: nhập 1000000 1000000007.
Nhóm 3. Đếm theo số dư
Phần tiêu đề “Nhóm 3. Đếm theo số dư”Bài 12. Cặp có tổng chia hết cho k
Phần tiêu đề “Bài 12. Cặp có tổng chia hết cho k”Dòng đầu có hai số n, k (n ≤ 105, k ≤ 105). Dòng thứ hai có n số nguyên dương a1, …, an (mỗi số không quá 109). Đếm số cặp chỉ số i nhỏ hơn j mà ai + aj chia hết cho k.
Ví dụ:
| Nhập | In ra |
|---|---|
4 31 2 3 4 | 2 |
Kiểm tra: nhập
7 43 7 5 1 9 11 4Bài 13. Đoạn con có tổng chia hết cho k
Phần tiêu đề “Bài 13. Đoạn con có tổng chia hết cho k”Dòng đầu có hai số n, k (n ≤ 105, k ≤ 109). Dòng thứ hai có n số nguyên a1, …, an (giá trị tuyệt đối không quá 109). Đếm số đoạn liên tiếp (gồm ít nhất một số) có tổng chia hết cho k.
Ví dụ:
| Nhập | In ra |
|---|---|
3 31 2 3 | 3 |
Kiểm tra: nhập
6 54 5 0 -2 -3 1Bài 14. Số dư phổ biến nhất
Phần tiêu đề “Bài 14. Số dư phổ biến nhất”Dòng đầu có hai số n, k (n ≤ 105, k ≤ 105). Dòng thứ hai có n số nguyên dương. Trong các số dư khi chia các số đó cho k, in ra số dư xuất hiện nhiều nhất và số lần xuất hiện. Nếu nhiều số dư cùng nhiều nhất thì chọn số dư nhỏ nhất.
Ví dụ:
| Nhập | In ra |
|---|---|
5 31 4 7 2 5 | 1 3 |
Kiểm tra: nhập
8 512 25 7 40 33 18 3 10Bài 15. Bội số toàn chữ số 1
Phần tiêu đề “Bài 15. Bội số toàn chữ số 1”Nhập số nguyên dương k (k ≤ 106, k không chia hết cho 2 và 5). Tìm số nhỏ nhất gồm toàn chữ số 1 (1, 11, 111, …) chia hết cho k, in ra số chữ số của nó.
Ví dụ: nhập 7 thì in ra 6.
Kiểm tra: nhập 2027.
Bài 16. Số chia các số dư cho trước
Phần tiêu đề “Bài 16. Số chia các số dư cho trước”Dòng đầu là t (t ≤ 5). Mỗi dòng trong t dòng tiếp theo có hai số m, r (0 ≤ r nhỏ hơn m). Tích các m không quá 106. Tìm số nguyên dương nhỏ nhất x sao cho x chia m dư r với mọi dòng. Nếu không có thì in ra -1.
Ví dụ:
| Nhập | In ra |
|---|---|
23 25 3 | 8 |
Kiểm tra: nhập
33 25 37 2Nhóm 4. Vận dụng
Phần tiêu đề “Nhóm 4. Vận dụng”Bài 17. Lũy thừa modulo
Phần tiêu đề “Bài 17. Lũy thừa modulo”Nhập ba số nguyên dương a, b, m (a ≤ 1018, b ≤ 106, m ≤ 109). In ra ab % m.
Ví dụ: nhập 3 20 1000 thì in ra 401.
Kiểm tra: nhập 2 1000000 1000000007.
Bài 18. Hai chữ số tận cùng của 2^n
Phần tiêu đề “Bài 18. Hai chữ số tận cùng của 2^n”Nhập số nguyên dương n (n ≤ 106). In ra hai chữ số tận cùng của 2n (in đủ hai chữ số, ví dụ 04).
Ví dụ: nhập 10 thì in ra 24.
Kiểm tra: nhập 2026.
Bài 19. Leo cầu thang
Phần tiêu đề “Bài 19. Leo cầu thang”Cầu thang có n bậc. Mỗi bước có thể leo 1, 2 hoặc 3 bậc. Nhập n (1 ≤ n ≤ 106), in ra số cách leo hết cầu thang, chia lấy dư cho 109 + 7.
Ví dụ: nhập 4 thì in ra 7.
Kiểm tra: nhập 1000.
Bài 20. Tổ hợp bằng tam giác Pascal
Phần tiêu đề “Bài 20. Tổ hợp bằng tam giác Pascal”Nhập hai số nguyên n, k (0 ≤ k ≤ n ≤ 1000). In ra số cách chọn k đồ vật từ n đồ vật khác nhau, chia lấy dư cho 109 + 7.
Ví dụ: nhập 5 2 thì in ra 10.
Kiểm tra: nhập 1000 500.
Bài 21. Tổng các tích từng cặp
Phần tiêu đề “Bài 21. Tổng các tích từng cặp”Dòng đầu là n (n ≤ 105). Dòng thứ hai có n số nguyên dương, mỗi số không quá 109. In ra tổng ai × aj với mọi cặp i nhỏ hơn j, chia lấy dư cho 109 + 7.
Ví dụ:
| Nhập | In ra |
|---|---|
41 2 3 4 | 35 |
Kiểm tra: nhập
51000000000 999999999 123456789 987654321 5