二分搜
對於一個函數 ,如果存在一個 x,對於所有 的 a, true,反之 false,基於這樣的單調性,就可以用二分搜。
1
2
3
4
5
6
7
8
9
10
11
12 | T binary_search()
{
while (L < R)
{
int M = (L + R) >> 1;
if (F(M))
R = M;
else
L = M + 1;
}
return L;
}
|
有些題目為 "最多/最少為何會成立",那麼如果你可以在良好的時間檢查出 " 如果代價是 ,那可不可以達成目標 ",並且 具有單調性,那麼你可以轉換成 " 如果代價是 ,那可不可以達成目標 " 傳換成 ,對答案(x)進行二分搜。
二分搜要注意兩件事,一個是無限迴圈,要避免它可以在腦中先模擬一下。一個是在實數中二分搜,因為實數的稠密性,題目會有誤差容忍值(例如 ),只要在誤差內都是容許的。
最小瓶頸樹
最小瓶頸樹
給定一張圖,求一顆生成樹,樹的最大邊權值最小。
枚舉最大邊權值 ,用 DFS/BFS 檢查 的邊是否可以將所有點相連,如果可以,最大邊權值會 ,否則會 。
三分搜
對於凸函數(例如 ),我們想要找尋其極值,意謂其左右兩側皆各自遞增/遞減,我們可以利用三分搜來解決(二分搜只能解決全體單調性,不能解決有兩邊的)。
考慮三分後從左到右四個採樣點的關係
-
,此時最小值一定不在最右邊
-
,此時最小值一定不在最右邊
-
,此時最小值一定不在最左邊
-
,此時最小值一定不在最左邊
這段描敘還可以再簡化:
-
,此時最小值一定不在最右邊
-
,此時最小值一定不在最左邊
每次迭代先求出 的值,再利用簡化過的規則,使區間減少 。令 為誤差容忍值,一開始的範圍為 ,迴圈大約需迭代 次( 大約為 )。
1
2
3
4
5
6
7
8
9
10
11
12
13 | const double EPS = 1e−7;
double trinary_search(double L, double R)
{
while (R - L > EPS)
{
double mL = (L + L + R) / 3, mR = (L + R + R) / 3;
if (f(mR) > f(mL))
R = mR;
else
L = mL;
}
return L;
}
|
另外一種做法,會固定迴圈迭代次數。
1
2
3
4
5
6
7
8
9
10
11
12 | double trinary_search(double L, double R)
{
for (int i = 0; i < 300; ++i)
{
double mL = (L + L + R) / 3, mR = (L + R + R) / 3;
if (f(mR) > f(mL))
R = mR;
else
L = mL;
}
return L;
}
|
UVa 01476 - Error Curves
給定 個開口向上的二次曲線 ,令 ,求 的最小值。
多個二次函數取最大值的結果還是一個二次函數,利用三分搜搜尋極值。
參考程式碼
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58 | #pragma GCC optimize("O2")
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
using ULL = unsigned long long;
using PII = pair<int, int>;
using PLL = pair<LL, LL>;
using VI = vector<int>;
using VVI = vector<vector<int>>;
const int INF = 1e9;
const int MXN = 1e4 + 5;
const int MXV = 0;
const double EPS = 1e-9;
const int MOD = 1e9 + 7;
#define MP make_pair
#define PB push_back
#define F first
#define S second
#define FOR(i, L, R) for (int i = L; i < (int)R; ++i)
#define FORD(i, L, R) for (int i = L; i > (int)R; --i)
#define IOS \
cin.tie(nullptr); \
cout.tie(nullptr); \
ios_base::sync_with_stdio(false);
int n;
double a[MXN], b[MXN], c[MXN];
double f(double x)
{
double ret = a[0] * x * x + b[0] * x + c[0];
FOR(i, 1, n) ret = max(ret, a[i] * x * x + b[i] * x + c[i]);
return ret;
}
double trinary_search(double L, double R)
{
for (int i = 0; i < 300; ++i)
{
double mL = (L + L + R) / 3, mR = (L + R + R) / 3;
if (f(mR) > f(mL))
R = mR;
else
L = mL;
}
return L;
}
int main()
{
int t;
cin >> t;
while (t--)
{
cin >> n;
FOR(i, 0, n) cin >> a[i] >> b[i] >> c[i];
cout << fixed << setprecision(4) << f(trinary_search(0.0, 1000.0)) << '\n';
}
}
|
UVa 10385 - Duathlon
今天有 個人要參加鐵人二項,有跑步和騎腳踏車,總長 公里,第 位參賽者賄賂主辦方,想要得到第一名,主辦方知道每個人跑步和騎腳踏車的速度 ,並且可以隨意決定跑步和騎腳踏車的距離,求第 位參賽者是否能獲得第一,如果可以,輸出領先第二名的秒數和主辦方設定跑步和騎腳踏車的距離。
設跑步 公里,那麼騎腳踏車 公里,第 位參賽者要花費 ,前 位參賽者花費時間取最小值,再減去第 位參賽者的時間,會是一個凸函數(可以參考這篇文章),同樣也能用三分搜找出極值
參考程式碼
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64 | #pragma GCC optimize("O2")
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
using ULL = unsigned long long;
using PII = pair<int, int>;
using PLL = pair<LL, LL>;
using VI = vector<int>;
using VVI = vector<vector<int>>;
const int INF = 1e9;
const int MXN = 1e4 + 5;
const int MXV = 0;
const double EPS = 1e-9;
const int MOD = 1e9 + 7;
#define MP make_pair
#define PB push_back
#define F first
#define S second
#define FOR(i, L, R) for (int i = L; i < (int)R; ++i)
#define FORD(i, L, R) for (int i = L; i > (int)R; --i)
#define IOS \
cin.tie(nullptr); \
cout.tie(nullptr); \
ios_base::sync_with_stdio(false);
int n;
double L;
double vr[MXN], vk[MXN];
double f(double r)
{
double t = r / vr[n - 1] + (L - r) / vk[n - 1];
double ret = r / vr[0] + (L - r) / vk[0];
FOR(i, 1, n - 1) ret = min(ret, r / vr[i] + (L - r) / vk[i]);
return ret - t;
}
double trinary_search(double L, double R) // find max
{
for (int i = 0; i < 300; ++i)
{
double mL = (L + L + R) / 3, mR = (L + R + R) / 3;
if (f(mL) > f(mR))
R = mR;
else
L = mL;
}
return L;
}
int main()
{
while (cin >> L >> n)
{
FOR(i, 0, n) cin >> vr[i] >> vk[i];
double ansL = trinary_search(0.0, L);
double ansT = f(ansL);
if (ansT < 0.00)
printf("The cheater cannot win.\n");
else
printf("The cheater can win by %.0lf seconds with r = %.2lfkm and "
"k = %.2lfkm.\n",
ansT * 3600.0, ansL, L - ansL);
}
}
|
例題練習