# 三分查找

# 基本性质

二分查找要求数组或范围满足一定意义上的有序性,三分查找则是针对数组或范围满足单峰或单谷的特性,即可能是先递增后递减,或先递减后递增。

# 三分套三分

针对高维空间,可以在固定前kk维的情况下对第k+1k+1维进行三分查找,这样一层层进行下去,也就是所谓的三分套三分。

# 练习题

# CF1394C - Boboniu and String (opens new window)

提示一

字符串的顺序并不影响所需要的操作次数,起作用的只有BN的个数。

提示二

如果两重三分都从00开始,有可能搜索到(0,0)(0,0)这个点,这是不符合题目要求的。

参考代码(C++)
#include <cstdio>
#include <iostream>
#include <set>
#include <vector>

using namespace std;
typedef long long ll;

template <typename T> void read(T &x) {
  x = 0;
  char c = getchar();
  T sig = 1;
  for (; !isdigit(c); c = getchar())
    if (c == '-')
      sig = -1;
  for (; isdigit(c); c = getchar())
    x = (x << 3) + (x << 1) + c - '0';
  x *= sig;
}

class Solution {
public:
  void solve() {
    int n;
    read(n);
    set<pair<int, int>> s;
    for (int i = 0; i < n; ++i) {
      string t;
      cin >> t;
      int b = 0;
      for (char c : t)
        b += c == 'B';
      s.insert({b, t.size() - b});
    };
    vector<pair<int, int>> vs(s.begin(), s.end());
    int m = vs.size();
    int tb = 0, tn = 0;
    for (auto p : vs)
      tb = max(tb, p.first), tn = max(tn, p.second);
    auto calc = [&](int B, int N) {
      int dist = 0;
      for (auto &[b, n] : vs) {
        if ((ll)(b - B) * (n - N) >= 0)
          dist = max(dist, max(abs(b - B), abs(n - N)));
        else
          dist = max(dist, abs(b - B) + abs(n - N));
      }
      return dist;
    };
    auto query = [&](int b, int lo) {
      int hi = tn;
      while (lo <= hi) {
        int ml = lo + (hi - lo) / 3, mr = hi - (hi - lo) / 3;
        int ql = calc(b, ml), qr = calc(b, mr);
        if (ql >= qr)
          lo = ml + 1;
        if (ql <= qr)
          hi = mr - 1;
      }
      int extreme = (lo + hi) / 2;
      int dist = calc(b, extreme);
      return make_pair(extreme, dist);
    };
    int lo = 1, hi = tb;
    while (lo <= hi) {
      int ml = lo + (hi - lo) / 3, mr = hi - (hi - lo) / 3;
      int ql = query(ml, 0).second, qr = query(mr, 0).second;
      if (ql >= qr)
        lo = ml + 1;
      if (ql <= qr)
        hi = mr - 1;
    }
    int ansb = (lo + hi) / 2;
    auto [ansn, dist] = query(ansb, 0);
    auto [ansn2, dist2] = query(0, 1);
    if (dist2 < dist) {
      dist = dist2;
      ansn = ansn2;
      ansb = 0;
    }
    printf("%d\n", dist);
    string ans = string(ansb, 'B') + string(ansn, 'N');
    printf("%s\n", ans.c_str());
  }
};

int main() {
  Solution solution = Solution();
  solution.solve();
}
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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90

# LC1515 - 服务中心的最佳位置 (opens new window)

提示

可以证明,平面上任意一点到所有给顶点的Euclidean距离之和是一个凸函数(证明过程详见官方题解 (opens new window)),因此可以使用三分套三分的方法求解。

参考代码(C++)

class Solution {
public:
    double getMinDistSum(vector<vector<int>>& positions) {
        double eps = 1e-7;
        auto dist = [&](double xc, double yc) {
            double ans = 0;
            for (const auto& pos: positions) {
                ans += sqrt((pos[0] - xc) * (pos[0] - xc) + (pos[1] - yc) * (pos[1] - yc));
            }
            return ans;
        };
        auto query = [&](double cx) {
            double l = 0, r = 100;
            while (r - l > eps) {
                double ml = l + (r - l) / 3, mr = r - (r - l) / 3;
                double dl = dist(cx, ml), dr = dist(cx, mr);
                if (dl > dr)
                    l = ml;
                else
                    r = mr;
            }
            return dist(cx, l);
        };
        double l = 0, r = 100, ans = 1e9;
        while (r - l > eps) {
            double ml = l + (r - l) / 3, mr = r - (r - l) / 3;
            double dl = query(ml), dr = query(mr);
            if (dl > dr)
                l = ml;
            else
                r = mr;
            ans = min(ans, min(dl, dr));
        }
        return ans;
    }
};
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