Tìm kiếm nhị phân theo đáp án
Tìm kiếm nhị phân thường được học để tìm một số trong dãy đã sắp xếp. Nhưng ý tưởng “chia đôi khoảng tìm kiếm” dùng được cho một loại bài rộng hơn nhiều: đề hỏi giá trị nhỏ nhất (hoặc lớn nhất) thỏa mãn một điều kiện, mà tìm thẳng giá trị đó thì khó, còn kiểm tra một giá trị cho trước có thỏa mãn không thì dễ. Khi đó ta đoán đáp án, kiểm tra, rồi thu hẹp khoảng đoán đi một nửa.
Ví dụ: chia sách
Phần tiêu đề “Ví dụ: chia sách”Có n cuốn sách xếp thành hàng, cuốn thứ i dày ai trang. Chia hàng sách thành k đoạn liên tiếp cho k bạn đọc. Bạn phải đọc nhiều nhất càng ít càng tốt: số trang lớn nhất mà một bạn phải đọc nhỏ nhất là bao nhiêu?
Tìm thẳng cách chia tốt nhất thì khó. Nhưng câu hỏi ngược lại dễ hơn nhiều: “nếu mỗi bạn đọc không quá x trang thì có chia được không?” Trả lời bằng tham lam: cho bạn thứ nhất nhận sách từ đầu hàng, nhận thêm khi tổng còn không quá x; không nhận được nữa thì chuyển sang bạn tiếp theo. Đếm số bạn cần, nếu không quá k thì chia được.
Gọi chia_duoc(x) là câu trả lời. Hàm này có tính chất quan trọng: x chia được thì mọi số lớn
hơn x cũng chia được (cho phép mỗi bạn đọc nhiều hơn thì càng dễ chia). Vậy khi đi x từ nhỏ đến
lớn, câu trả lời có dạng:
| x | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
|---|---|---|---|---|---|---|---|
| chia_duoc(x) | không | không | không | có | có | có | có |
Đáp án là vị trí đầu tiên chuyển từ “không” sang “có”. Tìm vị trí này bằng tìm kiếm nhị phân: thử ở giữa khoảng, nếu “có” thì đáp án ở nửa trái (gồm cả điểm giữa), nếu “không” thì ở nửa phải.
Với sách dày 7 2 5 10 8 và k = 2:
| Khoảng [thap, cao] | giữa | chia_duoc(giữa) | Khoảng mới |
|---|---|---|---|
| [10, 32] | 21 | có: 7 2 5 | 10 8 | [10, 21] |
| [10, 21] | 15 | không: cần 3 bạn | [16, 21] |
| [16, 21] | 18 | có: 7 2 5 | 10 8 | [16, 18] |
| [16, 18] | 17 | không: 7 2 5 | 10 | 8 | [18, 18] |
Đáp án 18. Khoảng ban đầu: x không thể nhỏ hơn cuốn dày nhất (10), và x bằng tổng mọi cuốn (32) thì chắc chắn chia được.
n, k = map(int, input().split())a = list(map(int, input().split()))
def chia_duoc(x): so_ban, dang_doc = 1, 0 for trang in a: if dang_doc + trang > x: # bạn hiện tại không nhận thêm được so_ban += 1 dang_doc = 0 dang_doc += trang return so_ban <= k
thap, cao = max(a), sum(a)while thap < cao: giua = (thap + cao) // 2 if chia_duoc(giua): cao = giua # giua thỏa mãn: đáp án là giua hoặc nhỏ hơn else: thap = giua + 1 # giua không thỏa mãn: đáp án lớn hơn giuaprint(thap)#include <iostream>#include <vector>#include <algorithm>using namespace std;
int n, k;vector<long long> a;
bool chiaDuoc(long long x) { int soBan = 1; long long dangDoc = 0; for (long long trang : a) { if (dangDoc + trang > x) { // bạn hiện tại không nhận thêm được soBan++; dangDoc = 0; } dangDoc += trang; } return soBan <= k;}
int main() { cin >> n >> k; a.resize(n); for (auto &x : a) cin >> x; long long thap = *max_element(a.begin(), a.end()), cao = 0; for (long long x : a) cao += x; while (thap < cao) { long long giua = (thap + cao) / 2; if (chiaDuoc(giua)) cao = giua; // đáp án là giua hoặc nhỏ hơn else thap = giua + 1; // đáp án lớn hơn giua } cout << thap << "\n"; return 0;}Mỗi lần kiểm tra mất n bước, khoảng tìm kiếm dài S (tổng số trang) nên cần khoảng log2S lần kiểm tra. Với n = 105 và S = 1014, cả bài khoảng 47 × 105 bước.
Khuôn mẫu chung
Phần tiêu đề “Khuôn mẫu chung”- Đặt câu hỏi có/không
kiem_tra(x): “đáp án không quá x có được không?” (hoặc “ít nhất x”). - Chứng minh tính đơn điệu: x thỏa mãn thì mọi x lớn hơn (hoặc nhỏ hơn) cũng thỏa mãn. Không có tính chất này thì không dùng được tìm kiếm nhị phân.
- Chọn khoảng [thap, cao] chắc chắn chứa đáp án.
- Thu hẹp cho đến khi thap = cao.
| Đề hỏi | Kiểm tra | Thu hẹp |
|---|---|---|
| x nhỏ nhất thỏa mãn | thỏa mãn thì cao = giua, không thì thap = giua + 1 | giua = (thap + cao) // 2 |
| x lớn nhất thỏa mãn | thỏa mãn thì thap = giua, không thì cao = giua - 1 | giua = (thap + cao + 1) // 2 |
Lưu ý: khi tìm x lớn nhất phải làm tròn lên (cộng 1 trước khi chia đôi). Nếu làm tròn xuống, với thap = 5, cao = 6 thì giua = 5, thỏa mãn thì thap = giua = 5, khoảng không đổi và vòng lặp chạy mãi không dừng.
Tìm kiếm nhị phân trên số thực
Phần tiêu đề “Tìm kiếm nhị phân trên số thực”Khi đáp án là số thực, khoảng không bao giờ thu về đúng một điểm. Thay vì while thap < cao, chỉ
cần lặp một số lần cố định, ví dụ 100 lần: mỗi lần khoảng giảm một nửa, sau 100 lần còn
1 / 2100 độ dài ban đầu, nhỏ hơn rất nhiều so với sai số cần in ra.
thap, cao = 0.0, 1e9for _ in range(100): giua = (thap + cao) / 2 if kiem_tra(giua): cao = giua else: thap = giuaprint(f"{thap:.6f}")Luyện tập
Phần tiêu đề “Luyện tập”Gồm 20 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. Tìm nhị phân trên số nguyên
Phần tiêu đề “Nhóm 1. Tìm nhị phân trên số nguyên”Bài 1. Căn bậc hai nguyên
Phần tiêu đề “Bài 1. Căn bậc hai nguyên”Nhập n (0 ≤ n ≤ 1018). In ra số nguyên x lớn nhất mà x × x ≤ n.
Ví dụ: nhập 50 thì in ra 7.
Kiểm tra: nhập 999999999999999999.
Bài 2. Xếp hình tam giác
Phần tiêu đề “Bài 2. Xếp hình tam giác”Có n viên bi (1 ≤ n ≤ 1018). Xếp thành tam giác: hàng 1 có 1 viên, hàng 2 có 2 viên, …, mỗi hàng phải đủ số viên. In ra số hàng nhiều nhất xếp được.
Ví dụ: nhập 20 thì in ra 5.
Kiểm tra: nhập 123456789012345678.
Bài 3. Căn bậc ba nguyên
Phần tiêu đề “Bài 3. Căn bậc ba nguyên”Nhập n (0 ≤ n ≤ 1018). In ra số nguyên x lớn nhất mà x × x × x ≤ n.
Ví dụ: nhập 1000 thì in ra 10.
Kiểm tra: nhập 987654321987654321.
Bài 4. Giai thừa có k chữ số 0
Phần tiêu đề “Bài 4. Giai thừa có k chữ số 0”Nhập k (1 ≤ k ≤ 1012). In ra số tự nhiên n nhỏ nhất sao cho n! có ít nhất k chữ số 0 ở tận cùng.
Ví dụ: nhập 6 thì in ra 25.
Kiểm tra: nhập 100000.
Nhóm 2. Kiểm tra bằng cách đếm
Phần tiêu đề “Nhóm 2. Kiểm tra bằng cách đếm”Bài 5. Máy photo
Phần tiêu đề “Bài 5. Máy photo”Có n máy photo, máy thứ i photo một bản mất ti giây, các máy chạy cùng lúc. Cần k bản. Dòng đầu có n, k (1 ≤ n ≤ 105, 1 ≤ k ≤ 109), dòng thứ hai có n số ti (1 ≤ ti ≤ 109). In ra số giây ít nhất để có đủ k bản.
Ví dụ:
| Nhập | In ra |
|---|---|
2 53 5 | 10 |
Kiểm tra: nhập
5 10000000007 3 1000000000 12 5Bài 6. Cắt gỗ
Phần tiêu đề “Bài 6. Cắt gỗ”Có n khúc gỗ, khúc thứ i dài ai mét. Cần cắt ra ít nhất k đoạn có cùng độ dài L mét (L nguyên dương), phần thừa bỏ đi, không được nối gỗ. Dòng đầu có n, k (1 ≤ n ≤ 105, 1 ≤ k ≤ 109), dòng thứ hai có n số ai (1 ≤ ai ≤ 109). In ra L lớn nhất, hoặc 0 nếu không cắt được.
Ví dụ:
| Nhập | In ra |
|---|---|
4 11802 743 457 539 | 200 |
Kiểm tra: nhập
6 25120 75 300 18 260 99Bài 7. Ăn kẹo
Phần tiêu đề “Bài 7. Ăn kẹo”Có n hộp kẹo, hộp thứ i có ai viên. Mỗi giờ bạn Na chọn một hộp và ăn tối đa v viên trong hộp đó (hộp còn ít hơn v viên thì ăn hết hộp, giờ đó không ăn thêm hộp khác). Na phải ăn hết kẹo trong h giờ. Dòng đầu có n, h (1 ≤ n ≤ h ≤ 109, n ≤ 105), dòng thứ hai có n số ai (1 ≤ ai ≤ 109). In ra v nhỏ nhất.
Ví dụ:
| Nhập | In ra |
|---|---|
4 83 6 7 11 | 4 |
Kiểm tra: nhập
5 2030 11 23 4 20Bài 8. Số thứ k trong bảng nhân
Phần tiêu đề “Bài 8. Số thứ k trong bảng nhân”Bảng nhân có n hàng, m cột, ô ở hàng i cột j ghi số i × j. Viết hết n × m số ra và sắp xếp tăng dần (giữ cả số trùng). Dòng duy nhất có n, m, k (1 ≤ n, m ≤ 105, 1 ≤ k ≤ n × m). In ra số thứ k.
Ví dụ: nhập 3 3 5 thì in ra 3.
Kiểm tra: nhập 1000 2000 1234567.
Bài 9. Số thứ k chia hết cho a hoặc b
Phần tiêu đề “Bài 9. Số thứ k chia hết cho a hoặc b”Nhập a, b, k (1 ≤ a, b ≤ 109, 1 ≤ k ≤ 109). Xét các số nguyên dương chia hết cho a hoặc chia hết cho b, xếp tăng dần. In ra số thứ k.
Ví dụ: nhập 4 6 5 thì in ra 16.
Kiểm tra: nhập 123456 654321 1000000000.
Bài 10. Số thứ k của hai dãy
Phần tiêu đề “Bài 10. Số thứ k của hai dãy”Dòng đầu có n, m, k (1 ≤ n, m ≤ 105, 1 ≤ k ≤ n + m). Dòng thứ hai có n số nguyên không giảm, dòng thứ ba có m số nguyên không giảm (|giá trị| ≤ 109). Gộp hai dãy và sắp xếp tăng dần, in ra số thứ k.
Ví dụ:
| Nhập | In ra |
|---|---|
3 4 41 5 92 3 8 10 | 5 |
Kiểm tra: nhập
5 5 7-100 -5 0 0 77-50 0 3 1000000000 1000000000Nhóm 3. Kiểm tra bằng tham lam
Phần tiêu đề “Nhóm 3. Kiểm tra bằng tham lam”Bài 11. Xe chở hàng
Phần tiêu đề “Bài 11. Xe chở hàng”Có n kiện hàng xếp theo thứ tự, kiện thứ i nặng wi kg. Mỗi ngày xe chở một chuyến gồm các kiện liên tiếp tiếp theo trong hàng, tổng không quá tải trọng xe. Phải chở xong trong D ngày. Dòng đầu có n, D (1 ≤ D ≤ n ≤ 105), dòng thứ hai có n số wi (1 ≤ wi ≤ 109). In ra tải trọng xe nhỏ nhất.
Ví dụ:
| Nhập | In ra |
|---|---|
10 51 2 3 4 5 6 7 8 9 10 | 15 |
Kiểm tra: nhập
8 330 12 45 7 60 21 9 50Bài 12. Chuồng bò
Phần tiêu đề “Bài 12. Chuồng bò”Có n chuồng trên một hàng thẳng, chuồng thứ i ở tọa độ xi (chưa sắp xếp, khác nhau). Cần nhốt c con bò vào c chuồng khác nhau sao cho khoảng cách nhỏ nhất giữa hai con bò bất kỳ lớn nhất có thể. Dòng đầu có n, c (2 ≤ c ≤ n ≤ 105), dòng thứ hai có n số xi (0 ≤ xi ≤ 109). In ra khoảng cách đó.
Ví dụ:
| Nhập | In ra |
|---|---|
5 31 2 8 4 9 | 3 |
Kiểm tra: nhập
8 40 100 3 47 52 99 20 75Bài 13. Bó hoa
Phần tiêu đề “Bài 13. Bó hoa”Có n bông hoa trồng thành hàng, bông thứ i nở vào ngày bi (từ ngày đó trở đi luôn nở). Cần làm m bó, mỗi bó gồm k bông liền nhau đã nở, mỗi bông chỉ dùng cho một bó. Dòng đầu có n, m, k (1 ≤ n ≤ 105, 1 ≤ m, k ≤ 105), dòng thứ hai có n số bi (1 ≤ bi ≤ 109). In ra ngày sớm nhất làm đủ m bó, hoặc -1 nếu không bao giờ làm đủ.
Ví dụ:
| Nhập | In ra |
|---|---|
7 2 37 7 7 7 12 7 7 | 12 |
Kiểm tra: nhập
10 3 25 1 9 3 3 8 2 10 4 6Bài 14. Xây thêm trạm xăng
Phần tiêu đề “Bài 14. Xây thêm trạm xăng”Trên đường cao tốc có n trạm xăng ở các km x1 nhỏ hơn x2 nhỏ hơn … nhỏ hơn xn. Được xây thêm nhiều nhất k trạm ở các km nguyên bất kỳ. Dòng đầu có n, k (2 ≤ n ≤ 105, 0 ≤ k ≤ 109), dòng thứ hai có n số xi tăng ngặt (0 ≤ xi ≤ 109). In ra khoảng cách lớn nhất giữa hai trạm kề nhau nhỏ nhất có thể đạt được (một số nguyên).
Ví dụ:
| Nhập | In ra |
|---|---|
3 20 10 30 | 10 |
Kiểm tra: nhập
6 50 7 100 135 140 1000Bài 15. Đèn đường
Phần tiêu đề “Bài 15. Đèn đường”Con đường là đoạn từ 0 đến L. Có n ngọn đèn ở các vị trí xi, mỗi đèn chiếu sáng mọi điểm cách nó không quá r. Dòng đầu có n, L (1 ≤ n ≤ 105, 1 ≤ L ≤ 109), dòng thứ hai có n số xi (0 ≤ xi ≤ L, chưa sắp xếp, có thể trùng). In ra số nguyên r nhỏ nhất để mọi điểm của con đường (kể cả điểm không nguyên) đều được chiếu sáng.
Ví dụ:
| Nhập | In ra |
|---|---|
3 102 9 5 | 2 |
Kiểm tra: nhập
6 1000120 990 400 401 700 15Bài 16. Nâng điểm thấp nhất
Phần tiêu đề “Bài 16. Nâng điểm thấp nhất”Lớp có n bạn, bạn thứ i được ai điểm. Thầy có k lượt cộng điểm, mỗi lượt cộng 1 điểm cho một bạn tùy chọn (không cần dùng hết). Dòng đầu có n, k (1 ≤ n ≤ 105, 0 ≤ k ≤ 109), dòng thứ hai có n số ai (0 ≤ ai ≤ 109). In ra điểm thấp nhất của lớp lớn nhất có thể sau khi cộng.
Ví dụ:
| Nhập | In ra |
|---|---|
4 53 7 1 4 | 4 |
Kiểm tra: nhập
6 10050 20 35 80 20 60Nhóm 4. Nâng cao
Phần tiêu đề “Nhóm 4. Nâng cao”Bài 17. Trung bình cộng lớn nhất
Phần tiêu đề “Bài 17. Trung bình cộng lớn nhất”Dòng đầu có n, L (1 ≤ L ≤ n ≤ 105), dòng thứ hai có n số nguyên (|ai| ≤ 106). Xét mọi đoạn liên tiếp có ít nhất L phần tử. In ra phần nguyên (làm tròn xuống) của trung bình cộng lớn nhất trong các đoạn đó.
Ví dụ:
| Nhập | In ra |
|---|---|
6 31 12 -5 -6 50 3 | 15 |
Kiểm tra: nhập
10 4-3 8 -1 6 -20 4 4 9 -2 5Bài 18. Trung vị lớn nhất
Phần tiêu đề “Bài 18. Trung vị lớn nhất”Trung vị của một đoạn gồm len số là số đứng ở vị trí thứ (len + 1) / 2 (làm tròn xuống) khi xếp đoạn đó tăng dần. Dòng đầu có n, k (1 ≤ k ≤ n ≤ 2 × 105), dòng thứ hai có n số nguyên (1 ≤ ai ≤ n). In ra trung vị lớn nhất trong các đoạn liên tiếp có ít nhất k số.
Ví dụ:
| Nhập | In ra |
|---|---|
5 31 2 3 2 1 | 2 |
Kiểm tra: nhập
10 43 9 1 8 7 2 10 6 4 5Bài 19. Leo núi
Phần tiêu đề “Bài 19. Leo núi”Bản đồ là bảng h hàng, w cột, mỗi ô ghi độ cao. Từ một ô được đi sang ô chung cạnh. Một đường đi có độ khó bằng chênh lệch độ cao lớn nhất giữa hai ô liên tiếp trên đường. Dòng đầu có h, w (1 ≤ h, w ≤ 300), h dòng sau mỗi dòng có w số (0 ≤ độ cao ≤ 106). In ra độ khó nhỏ nhất của đường đi từ ô trên cùng bên trái đến ô dưới cùng bên phải.
Ví dụ:
| Nhập | In ra |
|---|---|
3 31 2 23 8 25 3 5 | 2 |
Kiểm tra: nhập
4 510 12 30 31 4050 15 16 70 419 80 18 19 4590 91 99 21 20Bài 20. Giải phương trình bằng tìm kiếm nhị phân
Phần tiêu đề “Bài 20. Giải phương trình bằng tìm kiếm nhị phân”Nhập số nguyên c (0 ≤ c ≤ 109). In ra nghiệm thực x của phương trình x3 + x = c, làm tròn 6 chữ số sau dấu phẩy.
Ví dụ: nhập 10 thì in ra 2.000000.
Kiểm tra: nhập 123456789.