Tìm kiếm tam phân
Tìm kiếm nhị phân theo đáp án cần một câu hỏi có/không đơn điệu. Có những bài không như vậy: đề hỏi giá trị lớn nhất của một hàm mà hàm đó tăng dần rồi giảm dần, ví dụ doanh thu theo giá bán (giá thấp quá thì lãi ít, giá cao quá thì không ai mua). Tìm kiếm tam phân tìm đỉnh của hàm như vậy mà không cần thử mọi giá trị.
Ý tưởng
Phần tiêu đề “Ý tưởng”Hàm f trên đoạn [thap, cao] tăng rồi giảm (gọi là hàm một đỉnh). Chọn hai điểm m1 nhỏ hơn m2 bên trong đoạn rồi so f(m1) với f(m2):
- f(m1) nhỏ hơn f(m2): đỉnh không thể nằm bên trái m1. Nếu đỉnh ở bên trái m1 thì từ m1 sang m2 hàm đang giảm, f(m2) phải nhỏ hơn f(m1), mâu thuẫn. Bỏ phần [thap, m1).
- Ngược lại: đỉnh không nằm bên phải m2, lý luận tương tự. Bỏ phần (m2, cao].
Chọn m1, m2 chia đoạn thành ba phần bằng nhau thì mỗi lần bỏ được một phần ba. Đoạn dài N chỉ cần khoảng log1,5N lần so sánh, với N = 109 là khoảng 52 lần.
Ví dụ doanh thu: giá vé p nghìn đồng thì có 100 - p khách mua, doanh thu f(p) = p × (100 - p).
Trên số nguyên, đặt d = (cao - thap) / 3 (lấy phần nguyên), m1 = thap + d, m2 = thap + 2d. Bỏ phần trái thì thap = m1 + 1, bỏ phần phải thì cao = m2 - 1.
| thap | cao | m1 | m2 | f(m1) | f(m2) | Bỏ |
|---|---|---|---|---|---|---|
| 0 | 100 | 33 | 66 | 2211 | 2244 | phần trái, thap = 34 |
| 34 | 100 | 56 | 78 | 2464 | 1716 | phần phải, cao = 77 |
| 34 | 77 | 48 | 62 | 2496 | 2356 | phần phải, cao = 61 |
| … |
Cứ thế đến khi đoạn còn vài số thì thử hết, được đỉnh p = 50, doanh thu 2500.
Tìm kiếm tam phân trên số nguyên
Phần tiêu đề “Tìm kiếm tam phân trên số nguyên”Trên số nguyên có cách gọn hơn: f tăng rồi giảm nghĩa là f(x + 1) - f(x) dương rồi âm. Câu hỏi “f(x) có nhỏ hơn f(x + 1) không?” là đơn điệu (đúng, đúng, …, sai, sai), nên tìm điểm chuyển bằng tìm kiếm nhị phân quen thuộc: x đầu tiên mà f(x) ≥ f(x + 1) chính là đỉnh.
def f(p): return p * (100 - p)
thap, cao = 0, 100while thap < cao: giua = (thap + cao) // 2 if f(giua) < f(giua + 1): # còn đang tăng: đỉnh ở bên phải giua thap = giua + 1 else: # đã bắt đầu giảm (hoặc đứng yên): đỉnh là giua hoặc bên trái cao = giuaprint(thap, f(thap))#include <iostream>using namespace std;
long long f(long long p) { return p * (100 - p);}
int main() { long long thap = 0, cao = 100; while (thap < cao) { long long giua = (thap + cao) / 2; if (f(giua) < f(giua + 1)) thap = giua + 1; // còn đang tăng else cao = giua; // đã bắt đầu giảm } cout << thap << " " << f(thap) << "\n"; return 0;}Lưu ý: cách này đúng khi hàm không có đoạn bằng nhau trước khi tới đỉnh (tăng ngặt rồi giảm). Với hàm lồi (giảm rồi tăng, và mức giảm chậm dần), ví dụ tổng khoảng cách |x - a1| + … + |x - an|, các đoạn bằng nhau chỉ có thể xuất hiện tại đáy, nên tìm đáy vẫn đúng. Hàm có đoạn bằng ở lưng chừng thì không dùng được tìm kiếm tam phân.
Tìm kiếm tam phân trên số thực
Phần tiêu đề “Tìm kiếm tam phân trên số thực”Với số thực, lặp một số lần cố định (ví dụ 100 lần), mỗi lần bỏ một phần ba. Sau 100 lần đoạn chỉ còn (2/3)100 độ dài ban đầu, khoảng 2,5 × 10-18 lần.
Ví dụ: từ tấm bìa vuông cạnh a, cắt bốn góc bốn hình vuông cạnh x rồi gấp thành hộp không nắp. Thể tích V(x) = x × (a - 2x)2 với x từ 0 đến a/2: x nhỏ thì hộp quá thấp, x lớn thì đáy quá nhỏ, nên V tăng rồi giảm.
a = float(input())
def the_tich(x): return x * (a - 2 * x) * (a - 2 * x)
thap, cao = 0.0, a / 2for _ in range(100): m1 = thap + (cao - thap) / 3 m2 = cao - (cao - thap) / 3 if the_tich(m1) < the_tich(m2): thap = m1 else: cao = m2print(f"{the_tich(thap):.6f}")#include <cstdio>
double a;
double theTich(double x) { return x * (a - 2 * x) * (a - 2 * x);}
int main() { scanf("%lf", &a); double thap = 0, cao = a / 2; for (int lan = 0; lan < 100; lan++) { double m1 = thap + (cao - thap) / 3, m2 = cao - (cao - thap) / 3; if (theTich(m1) < theTich(m2)) thap = m1; else cao = m2; } printf("%.6f\n", theTich(thap)); return 0;}Với a = 6: hộp lớn nhất khi x = 1, thể tích 1 × 4 × 4 = 16.
Làm sao biết hàm có một đỉnh?
Phần tiêu đề “Làm sao biết hàm có một đỉnh?”Đây là phần khó nhất của bài, cần lý luận, không thể chỉ đoán. Vài dấu hiệu hay dùng:
| Dấu hiệu | Ví dụ |
|---|---|
| tổng các hàm lồi là hàm lồi | tổng khoảng cách tới n điểm, tổng bình phương khoảng cách |
| giá trị lớn nhất của các hàm lồi là hàm lồi | khoảng cách xa nhất tới n điểm |
| hàm bậc hai | p × (100 - p), (x - a)2 + (x - b)2 |
| một đại lượng tăng, một đại lượng giảm, lấy lớn hơn | max(k × a, (n - k) × b) |
Hàm lồi có đáy (giảm rồi tăng) thì tìm đáy; đổi dấu hàm (tìm đỉnh của -f) là quay về trường hợp tăng rồi giảm.
Luyện tập
Phần tiêu đề “Luyện tập”Gồm 16 bài, chia thành 2 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. Hàm trên số nguyên
Phần tiêu đề “Nhóm 1. Hàm trên số nguyên”Bài 1. Giá trị lớn nhất của hàm bậc hai
Phần tiêu đề “Bài 1. Giá trị lớn nhất của hàm bậc hai”Nhập a, b, c, L, R (1 ≤ a ≤ 1000, |b| ≤ 106, |c| ≤ 1012, -106 ≤ L ≤ R ≤ 106). In ra giá trị lớn nhất của f(x) = -a × x2 + b × x + c với x nguyên từ L đến R.
Ví dụ: nhập 1 10 3 -5 20 thì in ra 28.
Kiểm tra: nhập 7 -1234 999 -300000 -50.
Bài 2. Định giá vé
Phần tiêu đề “Bài 2. Định giá vé”Nếu giá vé là p nghìn đồng (p nguyên, 0 ≤ p ≤ N) thì có max(0, N - k × p) khách mua. Mỗi khách nhà xe tốn c nghìn đồng chi phí. Lợi nhuận là (p - c) × (số khách). Nhập N, k, c (1 ≤ N ≤ 109, 1 ≤ k ≤ 1000, 0 ≤ c ≤ 109). In ra lợi nhuận lớn nhất (có thể là 0 khi không bán được vé nào có lãi).
Ví dụ: nhập 100 2 10 thì in ra 800.
Kiểm tra: nhập 1000000000 7 35000000.
Bài 3. Điểm hẹn
Phần tiêu đề “Bài 3. Điểm hẹn”n bạn sống trên một con đường thẳng, bạn thứ i ở km xi. Chọn một km nguyên p làm điểm hẹn để tổng quãng đường mọi bạn phải đi (|x1 - p| + … + |xn - p|) nhỏ nhất. Dòng đầu có n (1 ≤ n ≤ 105), dòng thứ hai có n số xi (0 ≤ xi ≤ 109). In ra tổng quãng đường nhỏ nhất.
Ví dụ:
| Nhập | In ra |
|---|---|
52 9 4 15 7 | 18 |
Kiểm tra: nhập
10100 3 58 1000000000 17 64 64 250 9 41Bài 4. Tổng bình phương nhỏ nhất
Phần tiêu đề “Bài 4. Tổng bình phương nhỏ nhất”Dòng đầu có n (1 ≤ n ≤ 105), dòng thứ hai có n số nguyên (|ai| ≤ 106). Chọn số nguyên p để tổng (a1 - p)2 + … + (an - p)2 nhỏ nhất. In ra tổng đó.
Ví dụ:
| Nhập | In ra |
|---|---|
41 2 6 8 | 33 |
Kiểm tra: nhập
9-1000000 5 17 999999 -3 44 44 1000000 -250000Bài 5. Điểm hẹn trong thành phố
Phần tiêu đề “Bài 5. Điểm hẹn trong thành phố”Thành phố có đường kẻ ô vuông, đi từ (x, y) đến (X, Y) mất |x - X| + |y - Y| bước. n bạn ở các điểm (xi, yi). Chọn điểm hẹn (X, Y) có tọa độ nguyên để tổng số bước nhỏ nhất. Dòng đầu có n (1 ≤ n ≤ 105), n dòng sau mỗi dòng có x, y (|x|, |y| ≤ 109). In ra tổng số bước nhỏ nhất.
Ví dụ:
| Nhập | In ra |
|---|---|
31 54 16 7 | 11 |
Kiểm tra: nhập
60 010 -3-7 81000000000 25 5-1000000000 -1000000000Bài 6. Hai quầy thu ngân
Phần tiêu đề “Bài 6. Hai quầy thu ngân”Có N khách xếp hàng thanh toán ở hai quầy. Quầy một phục vụ mỗi khách mất a phút, quầy hai mất b phút. Chia k khách cho quầy một và N - k khách cho quầy hai (0 ≤ k ≤ N), hai quầy làm cùng lúc. Nhập N, a, b (1 ≤ N, a, b ≤ 109). In ra số phút ít nhất để phục vụ xong mọi khách.
Ví dụ: nhập 10 3 2 thì in ra 12.
Kiểm tra: nhập 1000000000 7 13.
Bài 7. Xếp hộp liền nhau
Phần tiêu đề “Bài 7. Xếp hộp liền nhau”Có n cái hộp trên một kệ dài, hộp thứ i ở ô số ai (các ô khác nhau). Cần dời các hộp sao cho chúng nằm ở n ô liền nhau p, p + 1, …, p + n - 1 (p nguyên tùy chọn). Dời một hộp từ ô u sang ô v tốn |u - v| công. Dòng đầu có n (1 ≤ n ≤ 105), dòng thứ hai có n số ai (|ai| ≤ 109). In ra tổng công nhỏ nhất.
Ví dụ:
| Nhập | In ra |
|---|---|
41 10 3 7 | 9 |
Kiểm tra: nhập
8-20 5 100 6 7 -1000000000 1000000000 50Nhóm 2. Hàm trên số thực
Phần tiêu đề “Nhóm 2. Hàm trên số thực”Bài 8. Hộp không nắp
Phần tiêu đề “Bài 8. Hộp không nắp”Từ tấm bìa hình vuông cạnh a, cắt ở bốn góc bốn hình vuông bằng nhau cạnh x rồi gấp thành hộp không nắp. Nhập a (1 ≤ a ≤ 100, số nguyên). In ra thể tích lớn nhất của hộp, làm tròn 6 chữ số sau dấu phẩy.
Ví dụ: nhập 6 thì in ra 16.000000.
Kiểm tra: nhập 97.
Bài 9. Hai chiếc thuyền
Phần tiêu đề “Bài 9. Hai chiếc thuyền”Hai chiếc thuyền chuyển động thẳng đều trên mặt hồ. Lúc t = 0, thuyền thứ nhất ở (x1, y1) với vận tốc (u1, v1) (sau t giây ở (x1 + u1 × t, y1 + v1 × t)), thuyền thứ hai tương tự. Dòng đầu có x1, y1, u1, v1, dòng thứ hai có x2, y2, u2, v2, dòng thứ ba có T (mọi số nguyên, giá trị tuyệt đối không quá 1000, T ≥ 0). In ra khoảng cách nhỏ nhất giữa hai thuyền trong khoảng thời gian từ 0 đến T, làm tròn 6 chữ số sau dấu phẩy.
Ví dụ:
| Nhập | In ra |
|---|---|
0 0 1 010 5 -1 020 | 5.000000 |
Kiểm tra: nhập
-300 120 7 -2450 -80 -9 5100Bài 10. Trạm bơm bên bờ sông
Phần tiêu đề “Bài 10. Trạm bơm bên bờ sông”Bờ sông là trục hoành. Có n ngôi nhà, nhà thứ i ở (xi, yi). Đặt một trạm bơm tại điểm (p, 0) và nối ống thẳng từ trạm tới từng nhà. Dòng đầu có n (1 ≤ n ≤ 104), n dòng sau mỗi dòng có x, y (|x|, |y| ≤ 104, số nguyên). In ra tổng chiều dài ống nhỏ nhất, làm tròn 6 chữ số sau dấu phẩy.
Ví dụ:
| Nhập | In ra |
|---|---|
20 38 3 | 10.000000 |
Kiểm tra: nhập
5-100 40250 -1030 9031 0-7 -200Bài 11. Chạy rồi bơi
Phần tiêu đề “Bài 11. Chạy rồi bơi”Bờ hồ là trục hoành, phía trên là đất, phía dưới là nước. Bạn An ở A = (0, a) trên bờ đất (a lớn hơn 0), cần đến B = (b, -c) dưới nước (c lớn hơn 0). An chạy với tốc độ v1 trên đất, bơi với tốc độ v2 dưới nước, đi thẳng từ A tới một điểm P trên bờ rồi bơi thẳng tới B. Nhập a, b, c, v1, v2 (số nguyên, 1 ≤ a, c, v1, v2 ≤ 1000, |b| ≤ 1000). In ra thời gian ngắn nhất, làm tròn 6 chữ số sau dấu phẩy.
Ví dụ: nhập 3 8 3 1 1 thì in ra 10.000000.
Kiểm tra: nhập 120 -700 45 13 2.
Bài 12. Trạm phát sóng bên đường
Phần tiêu đề “Bài 12. Trạm phát sóng bên đường”Con đường là trục hoành. Có n ngôi nhà ở (xi, yi). Đặt một trạm phát sóng trên đường sao cho khoảng cách từ trạm tới nhà xa nhất là nhỏ nhất. Dòng đầu có n (1 ≤ n ≤ 104), n dòng sau mỗi dòng có x, y (|x|, |y| ≤ 104, số nguyên). In ra khoảng cách đó, làm tròn 6 chữ số sau dấu phẩy.
Ví dụ:
| Nhập | In ra |
|---|---|
30 310 04 -4 | 5.450000 |
Kiểm tra: nhập
6-500 100900 -50120 7000 0-300 -650800 300Bài 13. Khoảng cách từ điểm tới đoạn thẳng
Phần tiêu đề “Bài 13. Khoảng cách từ điểm tới đoạn thẳng”Nhập 6 số nguyên px, py, ax, ay, bx, by (giá trị tuyệt đối không quá 104, A khác B). In ra khoảng cách ngắn nhất từ điểm P = (px, py) tới một điểm thuộc đoạn thẳng AB (gồm cả hai đầu), làm tròn 6 chữ số sau dấu phẩy.
Ví dụ: nhập 0 5 -3 0 3 0 thì in ra 5.000000.
Kiểm tra: nhập 100 -40 -250 30 60 900.
Bài 14. Chỗ tối nhất giữa hai ngọn đèn
Phần tiêu đề “Bài 14. Chỗ tối nhất giữa hai ngọn đèn”Hai ngọn đèn đặt ở hai đầu đoạn đường dài L: đèn một ở vị trí 0 có cường độ I1, đèn hai ở vị trí L có cường độ I2. Độ sáng tại vị trí x (0 nhỏ hơn x nhỏ hơn L) là I1 / x2 + I2 / (L - x)2. Nhập L, I1, I2 (số nguyên từ 1 đến 1000). In ra độ sáng nhỏ nhất trên đoạn đường, làm tròn 6 chữ số sau dấu phẩy.
Ví dụ: nhập 10 1 1 thì in ra 0.080000.
Kiểm tra: nhập 37 125 8.
Bài 15. Ném bóng xa nhất
Phần tiêu đề “Bài 15. Ném bóng xa nhất”Ném quả bóng từ độ cao h mét với vận tốc v m/s, góc ném θ so với mặt đất (0 ≤ θ ≤ 90 độ), gia tốc g = 9,8 m/s2, bỏ qua sức cản không khí. Bóng bay xa được v × cos θ × (v × sin θ + căn(v2 × sin2θ + 2gh)) / g mét. Nhập h, v (số nguyên, 0 ≤ h ≤ 1000, 1 ≤ v ≤ 1000). In ra quãng đường xa nhất, làm tròn 6 chữ số sau dấu phẩy.
Ví dụ: nhập 0 10 thì in ra 10.204082.
Kiểm tra: nhập 37 25.
Bài 16. Đường ngắn nhất chạm bờ
Phần tiêu đề “Bài 16. Đường ngắn nhất chạm bờ”Hai điểm A = (x1, y1) và B = (x2, y2) cùng ở phía trên trục hoành (y1, y2 lớn hơn 0). Đi từ A tới một điểm P trên trục hoành rồi tới B. Nhập x1, y1, x2, y2 (số nguyên, giá trị tuyệt đối không quá 104). In ra quãng đường AP + PB ngắn nhất, làm tròn 6 chữ số sau dấu phẩy.
Ví dụ: nhập 0 3 8 3 thì in ra 10.000000.
Kiểm tra: nhập -150 40 900 275.