第三場比賽 題解
=
>聽說吃拉麵能變強
>
# [z021](https://judge.cp.ccns.io/problem/z021):
令 $x$ 為 $m$ 個人過來**前**,長椅上目前**佔最多**人的人數
最大的 $k$ 為 $x + m$
而對於最小的 $k$:
若先去坐 $x$ 人的那個長椅,$k$ 會變大,所以 $m$ 個人應先分配到其他長椅上
於是求除了 $x$ 人的長椅外,還**剩下**多少位置(`rem`)
```cpp
for (int i = 0; i < n; i++) rem += x-a[i];
```
若 $m \le \text{rem}$ 則顯然 $k = x$
若 $m > \text{rem}$ 就將部份人分配到剩下的位置中(此時長椅人數都為 $x$),
接著將剩餘的人 $m-\text{rem}$ 平均分配到每張長椅上,$k = x + \lceil {m - \text{rem}\over n} \rceil$
以下完整程式碼:
```cpp
#include<bits/stdc++.h>
using namespace std;
int const maxn = 110;
int n, m, a[maxn];
int main() {
scanf("%d%d", &n, &m);
for(int i = 0; i < n; i++) scanf("%d", &a[i]);
int x = *max_element(a, a+n), rem = 0; // remainder
for(int i = 0; i < n; i++) rem += x-a[i];
if(m > rem) printf("%d ", x + (m-rem)/n + !!((m-rem)%n));
else printf("%d ", x);
printf("%d\n", x+m);
return 0;
}
```
# [z022](https://judge.cp.ccns.io/problem/z022):
二元二次方程式。
通常遇到方程式可以試試看「二分搜有理數」,而這題恰巧可以用上公式解。
遇到小數的時候,注意題目敘述,如果可以的話盡量多輸出幾位。
```cpp
#include <bits/stdc++.h>
#define Double long double
using namespace std;
int main() {
int T;
cin >> T;
while(T--) {
Double D, a, b;
cin >> D;
if(D == 1 || D == 2 || D == 3) { cout << "Null" << endl; continue; }
if(D >= 4) a = (D+sqrt(D*D-4*D)) / 2;
else if(D == 0) a = 0;
b = D - a;
printf("%.9llf %.9llf\n", a, b);
}
}
```
# [z023](https://judge.cp.ccns.io/problem/z023):
明顯的,如果採用枚舉的方式一定會吃個 **TLE** (寫之前最好估計一下複雜度!)
改用**二分搜**加速,每次抽籤就找他的 lower bound:
```cpp
int* l = lower_bound(LAN, LAN+N, lot);
```
以下完整程式碼:
```cpp
#include<bits/stdc++.h>
using namespace std;
int const maxn = 1e6 + 10;
int N, LAN[maxn], T;
int main() {
scanf("%d", &N);
for(int i = 0; i < N; i++) scanf("%d", &LAN[i]);
sort(LAN, LAN+N);
scanf("%d", &T);
while(T--) {
int lot;
scanf("%d", &lot);
int* l = lower_bound(LAN, LAN+N, lot);
if(*l == lot) printf("%d\n", lot);
else if(l == LAN+N) printf("%d no\n", *(l-1));
else if(l == LAN) printf("no %d\n", *l);
else printf("%d %d\n", *(l-1), *l);
}
return 0;
}
```
# [z024](https://judge.cp.ccns.io/problem/z024):
其實就是要找 $x$ 滿足 $f(x) = {{\sin(e^{-x})\cdot \sinh(x)}\over k} = 0.499$ 的地方
在題目中有提示 $k$ 越大,可以載重就越大,換句話說,$f$ 肯定是個遞增函數
因此用二分搜,`low = 0, up = 50`,開始搜,精度的問題請看 Code
```cpp
#include <bits/stdc++.h>
using namespace std;
int main() {
int n, i;
int t;
double low, up, mid, k;
cin >> n;
for (i = 0; i < n; i++) {
cin >> k;
low = 0, up = 50;
while (up - low > 1e-12) {
mid = (up + low) / 2;
if (sin(exp(-mid)) * sinh(mid) / k > 0.499)
up = mid;
else
low = mid;
}
cout << fixed << setprecision(9) << mid - 0.00000000049999999 << endl;
}
return 0;
}
```
# [z025](https://judge.cp.ccns.io/problem/z025):
由於詢問的次數很高,所以期待每次的詢問複雜度不能太高,需要設計方便處理詢問的流程。
名詞說明:
- group: 每一個點所屬的組別編號,並且最終在當編號等於自身號碼時代表找到根。
- Find: group,並且做扁平加速。
可以參照[第四週教材 Union-Find Forest](https://hackmd.io/s/SJ7nxwRL4#Union-Find-Forest-%E4%BD%B5%E6%9F%A5%E6%A3%AE%E6%9E%97)
```cpp
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5+5;
int n, m, q;
int group[maxn];
int Find(int x) {
if(group[x] == x) return x;
return group[x] = Find(group[x]);
}
int main() {
cin >> n >> m >> q;
for(int i = 1; i <= n; i++) group[i] = i;
for(int i = 0; i < m; i++) {
int a, b;
cin >> a >> b;
group[Find(a)] = Find(b);
}
for(int i = 0; i < q; i++) {
int x, y;
cin >> x >> y;
cout << (Find(x) == Find(y)) << endl;
}
return 0;
}
```