---
# System prepended metadata

title: Week 8：排序三、二分搜
tags: [S3 公開資源]

---

# Week 8：排序三、二分搜

###### tags: `S3 公開資源`

:::info
時間：03/19 9:00 ~ 17:00
地點：成大計算機與網路中心 **75201** 教室
主題：排序三、二分搜
直播連結：https://youtube.com/live/8OJJqBkUif0?feature=share
:::

# 必作題題解

[TOC]

## 二章 - 第七節

<!-- ### [題目](連結) -->

### [UVa 11286 - Conformity](http://domen111.github.io/UVa-Easy-Viewer/?11286)

撰寫者：Jun-an

這題要找出最受歡迎的「課程組合」的人數。

假設某個學生選了 $100$ $101$ $102$ $103$ $488$ 這五門課，
那麼選課組合就是 「$100$ $101$ $102$ $103$ $488$」。

不過每個課程組合都沒有照順序排，
因此計算每個課程組合有多少人選之前要先排序。

至於課程組合的部分，可以發現都是由 $5$ 個長度為 $3$ 的數字組成，
所以可以直接用 $long$ $long$ $int$ 儲存，或是你要用 $string$ 也行。

接著是要計算最受歡迎的課程組合，
排序過後會發現一樣的課程組合會排在一起，
因此只需要用一次 $for$ 迴圈掃過即可。

這裡補充一下，如果出現多種最受歡迎組合的選課人數一樣時，
需要全部加總。

:::spoiler 參考程式碼
```cpp=
#include <iostream>
#include <algorithm>

using namespace std;

int course[10005][5];
long long save[10005];

int main() {
    int n;

    while (cin >> n && n != 0) {
        for (int i = 0; i < n; ++i) {
            for (int j = 0; j < 5; ++j) {
                cin >> course[i][j];
            }

            sort(course[i], course[i] + 5);

            for (int j = 0; j < 5; ++j) {
                save[i] = save[i] * 1000 + course[i][j];
            }
        }

        sort(save, save + n);

        int mx = 1, cnt = 1, res = 1;

        for (int i = 1; i < n; ++i) {
            if (save[i] == save[i - 1]) {
                ++cnt;

                if (i == n - 1) {
                    if (cnt > mx) {
                        mx = cnt;
                        res = cnt;
                    }
                    
                    else if (cnt == mx) {
                        res += cnt;
                    }
                }
            }
            
            else {
                if (cnt > mx) {
                    mx = cnt;
                    res = cnt;
                }
                
                else if (cnt == mx){
                    res += cnt;
                }

                cnt = 1;
            }
        }

        cout << res << '\n';

        // clear.
        for (int i = 0; i < n; ++i) {
            for (int j = 0; j < 5; ++j) {
                course[i][j] = 0;
            }

            save[i] = 0;
        }
    }
}
```
:::

---

### [Kattis sidewayssorting - Sideways Sorting](https://open.kattis.com/problems/sidewayssorting)

撰寫者：小麥

字串先存陣列再做旋轉，這個時候可以排序，之後在去旋轉回來。

:::spoiler
```cpp=
#include <bits/stdc++.h>

using namespace std;

vector<string> arr;
vector<string> arr_rotated;

void solve() {
    int n, m;
    while(true) {
        arr.clear();
        arr_rotated.clear();

        cin >> n >> m;
        if (n == 0 || m == 0) {
            return;
        }

        arr.resize(n);
        arr_rotated.resize(m);

        for (int i = 0; i < n; i++) {
            cin >> arr[i];
        }
        for (int j = 0; j < m; j++) {
            for (int i = 0; i < n; i++) {
                arr_rotated[j] += arr[i][j];
            }
        }
        
        sort(arr_rotated.begin(), arr_rotated.end(), [](string x, string y) {
            for (int i = 0; i < x.length(); i++) {
                char x_lower = tolower(x[i]);
                char y_lower = tolower(y[i]);
                if (x_lower == y_lower) {
                    continue;
                }
                return x_lower < y_lower;
            }
            return false;
        });

        for (int j = 0; j < n; j++) {
            for (int i = 0; i < m; i++) {
                cout << arr_rotated[i][j];
            }
            cout << '\n';
        }
    }
}

int main() {
    solve();

    return 0;
}
```
:::

---

### [Kattis Sort of Sorting](https://open.kattis.com/problems/sortofsorting)

撰寫者：Eason

用內建sort寫cmp

:::spoiler 參考程式碼
```cpp=
#include<iostream>
#include<algorithm>
#define ll long long
using namespace std;

bool cmp(string a, string b){
    if (a[0] == b[0]){
        if (a[1] == b[1]){
                return false;
        }
        else{
                return a[1] < b[1];
        }
    }
    else{
        return a[0] < b[0];
    }
}

int main(){
    ios::sync_with_stdio(0), cin.tie(0);
    int n;
    while (cin >> n){
        if (n == 0) break;
        string name[205];
        for (int i = 0; i < n; i++){
                cin >> name[i];
        }
        stable_sort (name, name + n, cmp);
        for (int i = 0; i < n; i++){
                cout << name[i] << '\n';
        }
        cout << '\n';
    }
    return 0;
}
```
:::
    
---

## 二章 - 第八節

### [No - Judge 出現與否](https://hackmd.io/@sa072686/cp/%2FUEA2CFbIRqGvA_c9yaCP6Q#出現與否)

撰寫者：Eason

排序後直接二分搜

:::spoiler 參考程式碼
```cpp=
#include<iostream>
#include<algorithm>
using namespace std;

int main(){
    ios::sync_with_stdio(0), cin.tie(0);
    int n, q;
    cin >> n >> q;
    long long arr[100005];
    for (int i = 0; i < n; i++){
        cin >> arr[i];
    }
    sort (arr, arr + n);
    for (int i = 0; i < q; i++){
        long long temp, l = 0, r = n - 1;
        cin >> temp;
        while (l <= r){
            int mid = (l + r) / 2;
            if (temp == arr[mid]){
                cout << "YES\n";
                break;
            }
            else if (l == r){
                if (arr[mid] != temp) cout << "NO\n";
                else cout << "YES\n";
                break;
            }
            else if (temp < arr[mid]){
                r = mid - 1;
            }
            else if (temp > arr[mid]){
                l = mid + 1;
            }
        }
    }
    return 0;
}
```
:::

---

### [TOJ 47 - PB magic spell](https://toj.tfcis.org/oj/pro/47/)

撰寫者：Koying

二分搜 $a, b$

:::spoiler Code
```cpp=
#define MAXN 1000005
#define MAXM 1000005 
int n, m;
int x[MAXN];

void sol() {
    cin >> n;
    for (int i = 0; i < n; i++)
        cin >> x[i];
    sort(x, x + n);
    cin >> m;
    while (m--) {
        int k;
        cin >> k;
        int l = 0, r = n - 1;
        while (l < r) {
            int mid = (l + r + 1) / 2;
            if (x[mid] >= k)
                r = mid - 1;
            else
                l = mid;
        }

        if (l != n - 1 && x[l + 1] == k) {
            cout << k << '\n';
            continue;
        }
        else if (x[l] < k)
            cout << x[l] << ' ';
        else
            cout << "no ";

        r = n - 1;
        while (l < r) {
            int mid = (l + r) / 2;
            if (x[mid] <= k)
                l = mid + 1;
            else
                r = mid;
        }

        if (x[r] > k)
            cout << x[r] << '\n';
        else
            cout << "no\n";
    }
}
```
:::

---

### [UVa 10282 - Babelfish](http://domen111.github.io/UVa-Easy-Viewer/?10282)

撰寫者：Jun-an

利用字串大小進行排序，接著使用二分搜。

:::spoiler 參考程式碼
```cpp=
#include <iostream>
#include <algorithm>

using namespace std;

struct dict {
    string English, foreign;
} save[1000005];

int i;

bool cmp(dict a, dict b) {
    return a.foreign < b.foreign;
}

string Bsearch(string q) {
    int l = 0, r = i;

    while (l <= r) {
        int mid = l + (r - l) / 2;

        if (save[mid].foreign < q) {
            l = mid + 1;
        }

        else if (save[mid].foreign > q) {
            r = mid - 1;
        }

        else {
            return save[mid].English;
        }
    }

    return "eh";
}

int main() {
    string inp;
    i = 0;

    while(getline(cin, inp)) {
        if (inp.empty()) break;

        string tmp;
        for (int j = 0; j < inp.size(); ++j) {
            if (inp[j] == ' ') {
                save[i].English = tmp;
                tmp = "";
            }

            else {
                tmp += inp[j];
            }
        }
        save[i].foreign = tmp;

        ++i;
    }

    sort(save, save + i, cmp);

    string q;
    while (getline(cin, q)) {
        cout << Bsearch(q) << '\n';
    }
}
```
:::

---

### [ZJ c575 - APCS 2017-0304-4基地台](https://zerojudge.tw/ShowProblem?problemid=c575)

撰寫者：Koying

可以發現到，每個基地台的範圍越大，就越有機會成功。

將範圍當作 $x$，成功與否當作 $y$ 畫成圖可發現此函數具有單調性。

因此我們可以對基地台範圍二分搜，找到一個最小且可成功的範圍。

至於如何檢查合法與否？一個半徑為 $r$ 的基地台，其實就是從起點開始延伸 $2r$，所以我們可以利用一個變數紀錄目前這個基地台的範圍，如果服務點超出了範圍，再新架一個新的基地台，並延伸 $2r$

:::spoiler Code
```cpp=
#define MAXN 100005
#define MAXM 1000005 
int n, m;
int x[MAXN];

void sol() {
    cin >> n >> m;
    for (int i = 0; i < n; i++) {
        cin >> x[i];
    }
    sort(x, x + n);
    int l = 0, r = x[n - 1], mid = (l + r) / 2;
    while (l < r) {
        mid = (l + r) / 2;
        int now = 0, cnt = 0;
        for (int i = 0; i < n; i++) {
            if (x[i] > now) {
                now = x[i] + mid;
                cnt++;
            }
        }
        if (cnt > m) {
            l = mid + 1;
        }
        else
            r = mid;
    }
    cout << r << endl;
}
```
:::

---

### [AtCoder - abc146_c](https://atcoder.jp/contests/abc146/tasks/abc146_c)

撰寫者：Eason and 小麥

在 $1$~$10^{9}$ 二分搜出答案$N$

$a \times N + b \times$ ($N$的位數)

:::spoiler Eason
**38、41 行的1000000000LL 和 1LL 
是指long long 型態的1000000000 和 1
因為 max 和 min 的兩個參數型態要一樣**
```cpp=
#include<iostream>
#include<algorithm>
#include<math.h>
using namespace std;

int main(){
    ios::sync_with_stdio(0), cin.tie(0);
    long long a, b, x;
    cin >> a >> b >> x;
    int l = 1, r = 1000000000, prev_mid = 0;
    long long prev_cost = 1e10;

    while (l <= r){
        long long mid = (l + r) / 2;
        long long d = log10(mid) + 1;
        long long cost = a * mid + b * d;

        if (l == r){
            if (cost > x and prev_cost > x){
                cout << 0 << '\n';
            }
            else if (cost > x){
                cout << prev_mid << '\n';
            }
            else{
                cout << mid << '\n';
            }
            break;
        }

        if (cost == x){
            cout << mid << '\n';
            break;
        }
        else if (cost < x){
            prev_mid = mid;
            prev_cost = cost;
            l = min(1000000000LL, mid + 1);
        }
        else{
            r = max(1LL, mid - 1);
        }
    }
    return 0;
}
```
:::

:::spoiler 小麥
```cpp=
#include <bits/stdc++.h>
#define int long long

using namespace std;

int n, m, k;

bool test(int x) {
    int price = n * x + m * ((int) log10(x) + 1);
    return price <= k;
}

int binarySearch(int left, int right) {
    int result = 0;
    for (int i = (right - left) >> 1; i > 0; i >>= 1) {
        while (test(result + i)) {
            result += i;
        }
    }

    return result;
}

void solve() {
    cin >> n >> m >> k;   

    cout << (min((int) 1e9, binarySearch(0, 1e9 + 1))) << '\n';
}

signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0);

    solve();

    return 0;
}
```
:::

---

### [Kattis bootstrappingnumber](https://open.kattis.com/problems/bootstrappingnumber)

撰寫者：Jun-an

輸入一個整數 $n$，
找出一個數字 $x$，滿足 $x^x$ = $n$。

這裡利用二分搜在 $1$ ~ $n$ 之間找出 $x$，
浮點數的誤差值在 $1e$-$6$ 以內。

輸出的時候建議使用 `fix << precision(7) << x`

:::spoiler 參考程式碼
```cpp=
#include <iostream>
#include <cmath>
#include <iomanip>

using namespace std;

void Bsearch(double n) {
    double l = 0, r = n;

    for (int i = 0; i < 101; ++i) {
        double mid = l + (r - l) / 2;

        if (pow(mid, mid) < n) {
            l = mid;
        }

        else {
            r = mid;
        }
    }

    cout << fixed << setprecision(10) << l + (r - l) / 2 << '\n';
}

int main() {
    double n;
    cin >> n;

    Bsearch(n);
}
```
:::


---

### [TOJ 55](https://toj.tfcis.org/oj/pro/55/)

撰寫者：Eason

先排序！！
    
$N$的出現次數 = 第一個大於$N$的索引值 - 第一個大於等於$N$的索引值
這兩個都可以用二分搜求出來
![](https://i.imgur.com/YnjJjsr.png)
如上圖
左箭頭的索引值為 2
右箭頭的索引值為 5
則 5 的出現次數即為 5 - 2 = 3
<font color=red>但還有一個小問題</font>
如果我要找 16 呢？
:::spoiler Answer
其實就只要在陣列最後面加入一個無限大的數字就好
:::

:::spoiler code
```cpp=
#include<iostream>
#include<algorithm>
using namespace std;

long long arr[1000005];
int n;

int up_bound(long long tar){ // first > tar
    int l = 0, r = n, ind;
    while (l <= r){
        int mid = (l + r) / 2;
        if (l == r){
            ind = mid;
            break;
        }
        if (tar >= arr[mid]){
            l = mid + 1;
        }
        else{
            r = mid;
        }
    }
    return ind;
}

int lw_bound(long long tar){ // first >= tar
    int l = 0, r = n, ind;
    while (l <= r){
        int mid = (l + r) / 2;
        if (l == r){
            ind = mid;
            break;
        }
        if (tar > arr[mid]){
            l = mid + 1;
        }
        else{
            r = mid;
        }
    }
    return ind;
}

int main(){
    ios::sync_with_stdio(0), cin.tie(0);
    cin >> n;
    for (int i = 0; i < n; i++){
        cin >> arr[i];
    }
    arr[n] = 1000000000; // 創造一個無限大的數字放在陣列最後面
    sort (arr, arr + n);
    int q;
    cin >> q;
    for (int i = 0; i < q; i++){
        long long temp;
        cin >> temp;
        int tail = up_bound(temp);
        int head = lw_bound(temp);
        cout << tail - head << '\n';
    }
    return 0;
}
```
:::
:::spoiler 黑魔法
C++ algorithm標頭檔中有 upper_bound 和 lower_bound 就類似於上面 code 中 up_bound 和 lw_bound
```cpp=
#include<iostream>
#include<algorithm>
using namespace std;

long long arr[1000005];

int main(){
    ios::sync_with_stdio(0), cin.tie(0);
        int n;
    cin >> n;
    for (int i = 0; i < n; i++){
        cin >> arr[i];
    }
    sort (arr, arr + n);
    int q;
    cin >> q;
    for (int i = 0; i < q; i++){
        long long temp;
        cin >> temp;
        long long *tail = upper_bound(arr, arr + n, temp);
        long long *head = lower_bound(arr, arr + n, temp);
        cout << tail - head << '\n';
    }
    return 0;
}
```
:::
