Tìm kiếm tam phân tìm cực tiểu của hàm đơn đỉnh: giảm rồi tăng (hoặc tăng rồi giảm khi tìm cực đại). So giá trị tại hai điểm chia ba và bỏ đi một phần ba không thể chứa đáy. Trên số thực, chạy một số vòng cố định; trên số nguyên, cách gọn và an toàn hơn là chặt nhị phân trên hiệu . Phần khó thật sự là chứng minh hàm đơn đỉnh, thường bằng cách chỉ ra nó lồi.
Hàm lồi và tam phân trên số thực
Hàm trên đoạn là đơn đỉnh (theo cực tiểu) nếu có để giảm ngặt trên và tăng ngặt trên . Lấy trong đoạn: nếu thì đáy không thể nằm bên phải , bỏ ; ngược lại bỏ . Mỗi vòng đoạn còn .
Hàm lồi đơn đỉnh trừ khi có đoạn nằm ngang ở đáy, và đoạn ngang ở đáy không làm sai kết quả (mọi điểm ở đó đều là cực tiểu). Các phép giữ tính lồi hay gặp:
- , , là lồi; tổng các hàm lồi là lồi.
- của các hàm lồi là lồi: thời gian chờ người chậm nhất, khoảng cách tới điểm xa nhất.
- Hàm lồi theo mỗi biến khi cố định biến kia chưa đủ cho tam phân lồng; cần lồi theo cả hai biến cùng lúc, như khoảng cách Euclid.
Hàm đơn đỉnh nhưng có đoạn ngang không ở đáy (ví dụ hàm bậc thang) làm tam phân hỏng: không cho biết đáy ở phía nào.
Hỏi nhanh
Hàm nào không chắc đơn đỉnh trên đoạn?
của các hàm lồi thường có nhiều đáy, mỗi là một đáy. Ba hàm còn lại là tổng hoặc cực đại của các hàm lồi nên lồi.
Trên số thực, không dừng theo sai số : với tọa độ lớn, nhỏ có thể không bao giờ đạt được do làm tròn, vòng lặp chạy mãi. Chạy số vòng cố định: sau vòng đoạn dài , 100 vòng đã nhỏ hơn lần độ dài ban đầu. Khi đề yêu cầu in nhiều chữ số hoặc giá trị lớn, dùng long double.
Ví dụĐiểm Hẹn Nhanh Nhất
Đề. người trên trục số, người ở đi với vận tốc tối đa (tới ). Chọn điểm hẹn thực để thời gian người tới cuối cùng, , nhỏ nhất.
Lời giải. Mỗi lồi theo , nên cực đại của chúng cũng lồi: tam phân trên .
Bước 1: hàm cần cực tiểu. Thời gian của người tới cuối cùng khi hẹn ở , .
long double f(long double p) {
long double t = 0;
for (ll i = 1; i <= n; i++) t = max(t, fabsl(x[i] - p) / v[i]);
return t;
}
Bước 2: tam phân. Chia làm ba. Nếu f(m1) < f(m2) thì cực tiểu không thể nằm bên phải m2 (hàm lồi đã bắt đầu tăng), bỏ ; ngược lại bỏ . Mỗi vòng giữ lại khoảng; 200 vòng thu từ về cỡ , dư cho độ chính xác của long double.
for (ll it = 0; it < 200; it++) {
long double m1 = l + (r - l) / 3, m2 = r - (r - l) / 3;
if (f(m1) < f(m2)) r = m2;
else l = m1;
}
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll N = 6e4 + 5;
ll n, x[N], v[N];
long double f(long double p) {
long double t = 0;
for (ll i = 1; i <= n; i++) t = max(t, fabsl(x[i] - p) / v[i]);
return t;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cin >> n;
for (ll i = 1; i <= n; i++) cin >> x[i];
for (ll i = 1; i <= n; i++) cin >> v[i];
long double l = 1, r = 1e9;
for (ll it = 0; it < 200; it++) {
long double m1 = l + (r - l) / 3, m2 = r - (r - l) / 3;
if (f(m1) < f(m2)) r = m2;
else l = m1;
}
cout << fixed << setprecision(15) << f((l + r) / 2) << '\n';
}
3
1 11 21
1 2 1
10.000000000000000
phép.
Hàm mục tiêu tính bằng thuật toán khác
Nhiều khi không có công thức mà phải tính bằng cả một thuật toán: tham lam, Kadane, DP, đường đi ngắn nhất với tham số . Tam phân vẫn dùng được miễn chứng minh được lồi, thường bằng cách viết là cực đại (hoặc tổng) của rất nhiều hàm lồi đơn giản, mỗi hàm ứng với một lựa chọn rời rạc. Độ phức tạp là số vòng nhân chi phí một lần tính , nên phải chạy nhanh.
Ví dụĐộ Lệch Đoạn Liên Tiếp
Đề. Dãy số trong . Chọn số thực , trừ khỏi mọi phần tử; độ lệch là trị tuyệt đối lớn nhất của tổng một đoạn con. Tìm độ lệch nhỏ nhất.
Lời giải. Với mỗi đoạn cố định, lồi theo ; cực đại trên mọi đoạn vẫn lồi. Tam phân trên như ví dụ trước, chỉ khác cách tính .
Bước 1: bằng Kadane hai chiều. Trên dãy : hi là tổng lớn nhất của đoạn kết thúc tại , lo là nhỏ nhất. max(hi, 0) + b bỏ phần trước nếu nó âm, như Kadane thường; lo đối xứng. best là trị tuyệt đối lớn nhất của tổng một đoạn con.
long double f(long double x) {
long double hi = 0, lo = 0, best = 0;
for (ll i = 1; i <= n; i++) {
long double b = a[i] - x;
hi = max(hi, (long double)0) + b;
lo = min(lo, (long double)0) + b;
best = max(best, max(hi, -lo));
}
return best;
}
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll N = 2e5 + 5;
ll n, a[N];
long double f(long double x) {
long double hi = 0, lo = 0, best = 0;
for (ll i = 1; i <= n; i++) {
long double b = a[i] - x;
hi = max(hi, (long double)0) + b;
lo = min(lo, (long double)0) + b;
best = max(best, max(hi, -lo));
}
return best;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cin >> n;
for (ll i = 1; i <= n; i++) cin >> a[i];
long double l = -1e4, r = 1e4;
for (ll it = 0; it < 150; it++) {
long double m1 = l + (r - l) / 3, m2 = r - (r - l) / 3;
if (f(m1) < f(m2)) r = m2;
else l = m1;
}
cout << fixed << setprecision(15) << f((l + r) / 2) << '\n';
}
2
0 2
1.000000000000000
150 vòng, mỗi vòng hai lần .
Miền nguyên: chặt nhị phân trên hiệu kề
Trên số nguyên, lồi khi và chỉ khi hiệu không giảm. Cực tiểu là nhỏ nhất có , tìm bằng mẫu chặt nhị phân l, r, res quen thuộc, mỗi bước tính hai lần. Cách này tránh hẳn các chi tiết “dừng khi còn ba phần tử rồi vét” của tam phân nguyên.
Với hàm có giá trị thực (như ), hiệu kề tính bằng long double có thể sai dấu ở gần đáy, nơi hai giá trị gần như bằng nhau; khi đó tại điểm tìm được vẫn đủ gần cực tiểu theo sai số đề cho.
Hỏi nhanh
trên số nguyên . Đáp án là nào?
, , . Hiệu còn , nên là nhỏ nhất có hiệu không âm.
Ví dụNgưỡng Điều Chỉnh Chung
Đề. Hai dãy (dài ) và (dài ), , phần tử tới . Mỗi thao tác tăng hay giảm một phần tử 1 đơn vị. Cần đạt với ít thao tác nhất.
Lời giải. Chọn ngưỡng : kéo mọi lên , đẩy mọi xuống . Chi phí là tổng các hàm lồi. Với hàm lồi trên số nguyên, hiệu kề không giảm, nên chặt nhị phân trên hiệu kề thay cho tam phân.
Bước 1: chi phí một ngưỡng. ; chi phí tới , dùng long long.
ll f(ll t) {
ll s = 0;
for (ll i = 1; i <= n; i++)
if (a[i] < t) s += t - a[i];
for (ll j = 1; j <= m; j++)
if (b[j] > t) s += b[j] - t;
return s;
}
Bước 2: chặt hiệu kề. nhỏ nhất có f(t + 1) - f(t) >= 0: từ đó hàm không còn giảm, nên là cực tiểu.
while (l <= r) {
ll mid = (l + r) / 2;
if (f(mid + 1) - f(mid) >= 0) res = mid, r = mid - 1;
else l = mid + 1;
}
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll N = 1e5 + 5;
ll n, m, a[N], b[N];
ll f(ll t) {
ll s = 0;
for (ll i = 1; i <= n; i++)
if (a[i] < t) s += t - a[i];
for (ll j = 1; j <= m; j++)
if (b[j] > t) s += b[j] - t;
return s;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cin >> n >> m;
for (ll i = 1; i <= n; i++) cin >> a[i];
for (ll j = 1; j <= m; j++) cin >> b[j];
ll l = 1, r = 1e9, res = 1e9;
while (l <= r) {
ll mid = (l + r) / 2;
if (f(mid + 1) - f(mid) >= 0) res = mid, r = mid - 1;
else l = mid + 1;
}
cout << f(res) << '\n';
}
3 2
1 4 9
5 8
8
bước chặt, mỗi bước .
Ví dụMột Lần Điều Chỉnh
Đề. Chọn số nguyên để nhỏ nhất, với . In giá trị nhỏ nhất, sai số .
Lời giải. tuyến tính, lồi, tổng lồi theo . Đáy gần , không vượt , nên chặt hiệu kề như ví dụ trước trên , khoảng 41 bước.
Bước 1: tính bằng số thực. tới không vừa long long, và là số thực, nên dùng long double.
long double f(ll k) {
return (long double)k * B + A / sqrtl(k + 1);
}
Gần đáy, hai giá trị kề nhau gần như bằng nhau và hiệu có thể sai dấu do làm tròn; điểm tìm được vẫn nằm sát cực tiểu, sai số giá trị nhỏ hơn nhiều so với .
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
ll A, B;
long double f(ll k) {
return (long double)k * B + A / sqrtl(k + 1);
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cin >> A >> B;
ll l = 0, r = 2e12, res = 2e12;
while (l <= r) {
ll mid = (l + r) / 2;
if (f(mid + 1) - f(mid) >= 0) res = mid, r = mid - 1;
else l = mid + 1;
}
cout << fixed << setprecision(10) << f(res) << '\n';
}
20 1
12.9442719100
.
Tam phân lồng nhau
Hàm hai biến lồi theo cả hai biến cùng lúc thì cũng lồi. Tam phân ngoài trên , mỗi lần tính bằng một tam phân trong trên . Chi phí là tích số vòng hai tầng nhân chi phí một lần tính , nên giữ số vòng vừa đủ.
Ví dụĐường Tròn Bao Nhỏ Nhất
Đề. điểm nguyên trong . Tìm bán kính nhỏ nhất của đường tròn chứa mọi điểm.
Lời giải. Với tâm , bán kính cần là khoảng cách tới điểm xa nhất, một hàm lồi hai biến. Tâm tối ưu nằm trong hình vuông chứa các điểm. Tam phân lồng: vòng ngoài theo , vòng trong theo .
Bước 1: hàm hai biến. Bình phương khoảng cách tới điểm xa nhất. Bình phương đạt cực tiểu cùng chỗ với khoảng cách và vẫn lồi theo từng biến, nên khỏi gọi sqrt trong vòng lặp.
long double f(long double x, long double y) {
long double d = 0;
for (ll i = 1; i <= n; i++) d = max(d, (x - px[i]) * (x - px[i]) + (y - py[i]) * (y - py[i]));
return d;
}
Bước 2: tam phân trong. Với cố định, .
long double g(long double x) {
long double l = 0, r = 1000;
for (ll it = 0; it < 120; it++) {
long double m1 = l + (r - l) / 3, m2 = r - (r - l) / 3;
if (f(x, m1) < f(x, m2)) r = m2;
else l = m1;
}
return f(x, (l + r) / 2);
}
Bước 3: tam phân ngoài trên dùng g. lồi vì lồi theo cả hai biến cùng lúc. Cuối cùng lấy căn để ra bán kính.
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
ll n, px[55], py[55];
long double f(long double x, long double y) {
long double d = 0;
for (ll i = 1; i <= n; i++) d = max(d, (x - px[i]) * (x - px[i]) + (y - py[i]) * (y - py[i]));
return d;
}
long double g(long double x) {
long double l = 0, r = 1000;
for (ll it = 0; it < 120; it++) {
long double m1 = l + (r - l) / 3, m2 = r - (r - l) / 3;
if (f(x, m1) < f(x, m2)) r = m2;
else l = m1;
}
return f(x, (l + r) / 2);
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
cin >> n;
for (ll i = 1; i <= n; i++) cin >> px[i] >> py[i];
long double l = 0, r = 1000;
for (ll it = 0; it < 120; it++) {
long double m1 = l + (r - l) / 3, m2 = r - (r - l) / 3;
if (g(m1) < g(m2)) r = m2;
else l = m1;
}
cout << fixed << setprecision(15) << sqrtl(g((l + r) / 2)) << '\n';
}
4
0 0
0 2
2 0
2 2
1.414213562373095
phép.
Khi đi thi
- Trước khi tam phân, chứng minh hàm lồi bằng các quy tắc: tổng, cực đại, hàm tuyến tính cộng hàm lồi. Hàm bậc thang hay của nhiều hàm thì không dùng được.
- Số thực: chạy 100 đến 200 vòng cố định, không dừng theo ; dùng
long doublekhi in nhiều chữ số. - Số nguyên: chặt nhị phân nhỏ nhất có ; nếu đáy có công thức gần đúng, chỉ cần thử vài giá trị quanh đó.
Tóm tắt
- Tam phân giữ đoạn chứa đáy bằng cách so , ; mỗi vòng còn .
- Hàm lồi là đơn đỉnh; tổng và cực đại của hàm lồi vẫn lồi.
- Trên số nguyên, cực tiểu của hàm lồi là điểm đầu tiên hiệu kề không âm.
- Hàm lồi hai biến: tam phân lồng, tầng trong tính .
Bài tập
Đọc thêm
- Tìm kiếm tam phânVNOI WikiBản dịch e-maxx bằng tiếng Việt.
- Ternary Searchcp-algorithmsTam phân liên tục và rời rạc, điều kiện đơn đỉnh.
- Optimizing Unimodal FunctionsUSACO GuideCách áp dụng, sai số và bài luyện.
- 二分OI WikiMục 三分法: tam phân cùng chặt nhị phân trên đạo hàm.