Bỏ qua để đến nội dung

Tìm kiếm nhị phân theo đáp án

Mức độ: Nâng cao

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.

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:

x10111213141516
chia_duoc(x)khôngkhôngkhôngcó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ữachia_duoc(giữa)Khoảng mới
[10, 32]21có: 7 2 5 | 10 8[10, 21]
[10, 21]15không: cần 3 bạn[16, 21]
[16, 21]18có: 7 2 5 | 10 8[16, 18]
[16, 18]17khô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 giua
print(thap)

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.

  1. Đặ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”).
  2. 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.
  3. Chọn khoảng [thap, cao] chắc chắn chứa đáp án.
  4. Thu hẹp cho đến khi thap = cao.
Đề hỏiKiểm traThu hẹp
x nhỏ nhất thỏa mãnthỏa mãn thì cao = giua, không thì thap = giua + 1giua = (thap + cao) // 2
x lớn nhất thỏa mãnthỏa mãn thì thap = giua, không thì cao = giua - 1giua = (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.

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, 1e9
for _ in range(100):
giua = (thap + cao) / 2
if kiem_tra(giua):
cao = giua
else:
thap = giua
print(f"{thap:.6f}")

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ậ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.

Xem đáp số

999999999

Hướng dẫn giải chi tiết: liên hệ tác giả.

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.

Xem đáp số

496903992

Hướng dẫn giải chi tiết: liên hệ tác giả.

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.

Xem đáp số

995867

Hướng dẫn giải chi tiết: liên hệ tác giả.

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.

Xem đáp số

400005

Hướng dẫn giải chi tiết: liên hệ tác giả.

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ậpIn ra
2 5
3 5
10

Kiểm tra: nhập

5 1000000000
7 3 1000000000 12 5
Xem đáp số

1316614420

Hướng dẫn giải chi tiết: liên hệ tác giả.

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ậpIn ra
4 11
802 743 457 539
200

Kiểm tra: nhập

6 25
120 75 300 18 260 99
Xem đáp số

32

Hướng dẫn giải chi tiết: liên hệ tác giả.

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ậpIn ra
4 8
3 6 7 11
4

Kiểm tra: nhập

5 20
30 11 23 4 20
Xem đáp số

5

Hướng dẫn giải chi tiết: liên hệ tác giả.

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.

Xem đáp số

531360

Hướng dẫn giải chi tiết: liên hệ tác giả.

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.

Xem đáp số

103860316030464

Hướng dẫn giải chi tiết: liên hệ tác giả.

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ậpIn ra
3 4 4
1 5 9
2 3 8 10
5

Kiểm tra: nhập

5 5 7
-100 -5 0 0 77
-50 0 3 1000000000 1000000000
Xem đáp số

3

Hướng dẫn giải chi tiết: liên hệ tác giả.

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ậpIn ra
10 5
1 2 3 4 5 6 7 8 9 10
15

Kiểm tra: nhập

8 3
30 12 45 7 60 21 9 50
Xem đáp số

87

Hướng dẫn giải chi tiết: liên hệ tác giả.

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ậpIn ra
5 3
1 2 8 4 9
3

Kiểm tra: nhập

8 4
0 100 3 47 52 99 20 75
Xem đáp số

25

Hướng dẫn giải chi tiết: liên hệ tác giả.

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ậpIn ra
7 2 3
7 7 7 7 12 7 7
12

Kiểm tra: nhập

10 3 2
5 1 9 3 3 8 2 10 4 6
Xem đáp số

6

Hướng dẫn giải chi tiết: liên hệ tác giả.

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ậpIn ra
3 2
0 10 30
10

Kiểm tra: nhập

6 5
0 7 100 135 140 1000
Xem đáp số

144

Hướng dẫn giải chi tiết: liên hệ tác giả.

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ậpIn ra
3 10
2 9 5
2

Kiểm tra: nhập

6 1000
120 990 400 401 700 15
Xem đáp số

150

Hướng dẫn giải chi tiết: liên hệ tác giả.

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ậpIn ra
4 5
3 7 1 4
4

Kiểm tra: nhập

6 100
50 20 35 80 20 60
Xem đáp số

56

Hướng dẫn giải chi tiết: liên hệ tác giả.

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ậpIn ra
6 3
1 12 -5 -6 50 3
15

Kiểm tra: nhập

10 4
-3 8 -1 6 -20 4 4 9 -2 5
Xem đáp số

4

Hướng dẫn giải chi tiết: liên hệ tác giả.

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ậpIn ra
5 3
1 2 3 2 1
2

Kiểm tra: nhập

10 4
3 9 1 8 7 2 10 6 4 5
Xem đáp số

7

Hướng dẫn giải chi tiết: liên hệ tác giả.

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ậpIn ra
3 3
1 2 2
3 8 2
5 3 5
2

Kiểm tra: nhập

4 5
10 12 30 31 40
50 15 16 70 41
9 80 18 19 45
90 91 99 21 20
Xem đáp số

3

Hướng dẫn giải chi tiết: liên hệ tác giả.

Bà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.

Xem đáp số

497.933190

Hướng dẫn giải chi tiết: liên hệ tác giả.