# 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; } ``` :::