---
tags: 成大高階競技程式設計 2021
image: https://i.imgur.com/IIj9BcL.png
---
# 2021 高階競程 Contest #2 - 題解
## [無線通訊網路](https://judge.csie.ncku.edu.tw/problems/94)
- Task Provider: leo
- Task Setter: leo
### 首殺 team21_28 (00:28)
### 解法說明
「**保證任兩個不同的基地台都有辦法互相傳輸資料,
且兩基地台之間傳輸資料的路徑唯一。**」
題目敘述最後的這句話是關鍵,
因為這句話代表了一張圖是聯通的,
且兩點之間的路徑唯一。
由這兩點可以推導出題目給定的是一棵樹,
那麼樹的著色問題就簡單了。
當節點只有一個時,僅需一種顏色即可;
如果大於一個節點也只需兩種顏色。
可以看成你將樹隨便選一個根,
第一層塗成顏色 $A$,第二層塗成顏色 $B$,第三層塗成顏色 $A$,...
即可將所有節點著色完畢,且相鄰節點顏色不同。
P.S. 題目測資的輸入中,$M$ 其實都是 $N-1$,題目中 $M$ 的範圍算是一種誤導
### 標準程式
:::spoiler
```cpp
#include <iostream>
using namespace std;
int main() {
int n;
cin >> n;
cout << (n == 1 ? 1 : 2) << "\n";
}
```
:::
## [我叫做偉杰,是個會計師](https://judge.csie.ncku.edu.tw/problems/95)
- Task Provider: ys
- Task Setter: ys
### 首殺 team21_09 (00:42)
### 解法說明
#### 解法 1
觀察當 $a$ 長度為 $3$ 的情況 (因為足夠小 以便摸索解法)
如 $a = (x, -1, y)$
不失一般性的設 $x < y$
1. 若 $k \le x$ 則 $m \ge y-x$
2. 若 $k \ge y$ 則 $m \ge y-x$
3. 若 $x < k < y$ 則 $m = \max(k-x, y-k) < y-x$
所以要將 $k$ 落在 3. 的條件下才使 $m$ 盡量小
再細看 $x < k < y$
從 $k = x+1$ 遞增至 $y-1$
會發現 $k$ 與 $x$ 絕對差越來越大,而與 $y$ 絕對差越來越小
在這之中 $k$ 與 $x, y$ 的兩個絕對差會逐步至一個**最小對**,接著再增大
> $m$ 則從這對中找最大的那個
反之從 $k = y-1$ 遞減至 $x+1$ 也一樣
這貌似是個類似二次函數的凸函數 ($f: k \mapsto m$) (只有一個**局部極值**)
若是能證明任意長度的 $a$ 都有此函數特性,那麼可以用**三分搜**解決
不用管**不與 $k$ 相鄰的絕對差**,
因為不與 $k$ 相鄰的絕對差之中的最大值 $t$ 永遠都不會變動,
也就是說若函數擁有理想上的特性,那麼 $m$ 在遞減時也只會降到 $t$。
而**與 $k$ 相鄰的絕對差**,也只需要關心最大值 $y$ 與最小值 $x$
因為若存在介於 $x, y$ 的數 $z$ 使得 $|k-z| > \max(|k-y|, |k-x|)$
則若 $k \ge z$ 得 $k > x$ (因 $x$ 是最小值),但 $k-z > k-x \implies z < x$ 矛盾
或若 $k \le z$ 得 $k < y$ (因 $y$ 是最大值),但 $z-k > y-k \implies z > y$ 矛盾
> $|k-z|$ 帶絕對值,所以要拆兩狀況論證
也就是說,任意長度的 $a$ 可類比為長度 $3$ 的情況
#### 解法 2
根據**解法 1** 的結論,只需找出與 $k$ 相鄰數字中的最大值 $y$ 與最小值 $x$
則 $m = \max(y - k, k - x)$,可根據該式直接計算出 $k$ 為多少
題目要求 $m$ 的最小值,則該值為 $m' = y - k$ 與 $m' = k - x$ 的交點
$$
\begin{split}
&\implies y - k = k - x \\
&\implies k = {{x+y} \over 2}
\end{split}
$$
> 解法 1 沒有給出 $k$ 應為多少,得用找的
#### 解法 3
根據**解法 1** 的結論,$m$ 對 $k$ 的函數是類似二次函數的凸函數
也就是說,該函數的斜率是**單調的**,所以可以用**二分搜**來找 $k$
實作上,利用解法 1 的程式 `double m(double k)`
透過 `m(k+0.01) - m(k) > 0` 就能知道在 `k` 上 `m(k)` 的**變化**是否為遞增
> 此變化量也可以看做是該函數在 $k$ 上的微分
### 標準程式
:::spoiler 解法 1
```cpp
#include<bits/stdc++.h>
using namespace std;
int constexpr maxn = 5e5 + 10;
int n;
vector<double> a;
double m(double k) {
double mx = 0; // maximum of absolute differences
for(int i = 0; i+1 < n; i++) {
double x = a[ i ]==-1? k : a[ i ];
double y = a[i+1]==-1? k : a[i+1];
mx = max(mx, abs(x - y));
}
return mx;
}
int main()
{
scanf("%d", &n);
a.resize(n);
for(double &v: a) scanf("%lf", &v);
double l = 0, r = 1e9+10;
while(r - l > 0.01) {
double k1 = l + (r-l)/3, k2 = r - (r-l)/3;
if(m(k1) > m(k2)) l = k1;
else r = k2;
}
printf("%.1lf\n", m(l));
return 0;
}
```
:::
:::spoiler 解法 2
```cpp
#include<bits/stdc++.h>
using namespace std;
int constexpr maxn = 5e5 + 10;
int n;
double a[maxn];
int main()
{
scanf("%d", &n);
for(int i = 0; i < n; i++) scanf("%lf", &a[i]);
double mx = 0, mi = 1e9;
for(int i = 0; i < n; i++) {
bool c1 = i > 0 && a[i-1] == -1 && a[i] != -1;
bool c2 = i < n-1 && a[i+1] == -1 && a[i] != -1;
if(c1 || c2) mx = max(mx, a[i]), mi = min(mi, a[i]);
}
double m = 0, k = (mx+mi)/2.0;
for(int i = 0; i < n; i++) if(a[i] == -1) a[i] = k;
for(int i = 0; i+1 < n; i++) m = max(m, abs(a[i]-a[i+1]));
printf("%.1lf\n", m);
return 0;
}
```
:::
:::spoiler 解法 3
```cpp
#include<bits/stdc++.h>
using namespace std;
int const maxn = 5e5 + 10;
int n;
vector<double> a;
double m(double k) {
double mx = 0; // maximum of absolute differences
for(int i = 0; i+1 < n; i++) {
double x = a[ i ]==-1? k : a[ i ];
double y = a[i+1]==-1? k : a[i+1];
mx = max(mx, abs(x - y));
}
return mx;
}
int main()
{
scanf("%d", &n);
a.resize(n);
for(double &v: a) scanf("%lf", &v);
double l = 0, r = 1e9+10;
while(r - l > 0.01) {
double k = (l + r)/2;
if(m(k+0.01) - m(k) > 0) r = k;
else l = k+0.01;
}
printf("%.1lf\n", m(l));
return 0;
}
```
:::
## [驗收成果](https://judge.csie.ncku.edu.tw/problems/96)
- Task Provider: D4nnyLee
- Task Setter: D4nnyLee
### 首殺 team21_12 (02:30)
### 解法說明
這題可以用**二分搜**來解。
首先我們先定義 $seg$ 代表所有合併前的陣列,$seg_i$ 代表第 $i$ 個陣列,
然後再寫一個函式 $cnt$,使得 $cnt(seg, x)$ 是所有 $seg$ 的陣列中,小於 $x$ 的元素數量。
因為每個 $seg_i$ 都有規律,所以要算出 $cnt(seg, x)$ 其實不難。
有了 $cnt$ 之後,可以發現這題的答案有**單調性**,
因為 $cnt(seg, x) \leq cnt(seg, x + 1)$ 一定成立。
假設最後的答案為 $ans$,則 $cnt(seg, ans) \leq k$ 必定成立。
> 為什麼會有小於 $k$ 的情況呢?
>
> 用例子來做解釋:
> 假設 $[1, 1, 2, 2, 2, 3, 3, 3]$ 為所有區間合併並排序完後的數列,令 $k = 7$,
> 現在索引值為 $k$ 的元素的值是 $3$,但是所有小於 $3$ 的元素只有 $5$ 個。
題目要找的就是一個最大的整數 $ans$,使得 $cnt(seg, ans) \leq k$。
### 標準程式
> 因為數字的範圍很大,用 `int` 很容易遇到整數溢位(Integer Overflow)的問題,
> 所以下面實作都使用 `long long`。
:::spoiler
```cpp
#include <bits/stdc++.h>
using namespace std;
long long cnt(const vector<pair<long long, long long>>& seg, long long x) {
long long num{0};
for (auto [l, r] : seg)
if (x > l) num += min(x - 1, r) - l + 1;
return num;
}
int main(void) {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, k;
cin >> n >> k;
vector<pair<long long, long long>> seg(n);
for (auto &[l, r] : seg)
cin >> l >> r;
long long l{-2000000000}, r{2000000000 + 1};
while (r - l > 1) {
long long mid{(l + r) / 2};
if (cnt(seg, mid) <= k) l = mid;
else r = mid;
}
cout << l << '\n';
return 0;
}
```
:::
## [著色遊戲](https://judge.csie.ncku.edu.tw/problems/96)
- Task Provider: alan8585
- Task Setter: leo
### 首殺 -- (-:-)
### 解法說明
可以把每個格子都當成一個獨立節點,
若兩個節點相鄰的話可以合併成一個集合,
講到這邊會想到「併查集」這個資料結構。
因為併查集支援查詢與合併,
因此可以將所有輸入都讀進來,
並且反向操作,題目變成,
「給定一張最終的圖,每次操作將一個格子塗成白色,
請問每次操作後會有多少聯通塊,以及最大聯通塊大小為何」。
只要每次塗成白色,就將該節點與周圍白色的節點合併,
並且在合併過程取最大值即可。
時間複雜度為輸入的 $O(NM)$,
加上每次合併查詢操作的 $O(\alpha(NM))$,
乘上操作次數 $Q$,總複雜度為 $O(NM+Q\cdot\alpha(NM))$。
### 標準程式
:::spoiler
```cpp
#include <iostream>
#include <utility>
#include <bitset>
#define F first
#define S second
using namespace std;
const int MAXN = 3001, dx[4] = {1, 0, -1, 0}, dy[4] = {0, 1, 0, -1};
bitset<MAXN> a[MAXN];
int n, m, boss[MAXN * MAXN], sz[MAXN * MAXN], mx, cnt;
pair<int, int> que[200000], ans[200000];
int find(int x){
if(boss[x] == x)
return x;
return boss[x] = find(boss[x]);
}
void combine(int x, int y){
x = find(x), y = find(y);
if(x == y)
return;
sz[x] += sz[y];
boss[y] = x;
cnt--;
mx = max(mx, sz[x]);
}
bool check(int x, int y){
return x > 0 && x <= n && y > 0 && y <= m && !a[x][y];
}
int trans(int x, int y){
return x * m + y;
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
int q;
cin >> n >> m >> q;
for(int i = 1; i <= n; i++)
for(int j = 1, x; j <= m; j++)
cin >> x, a[i][j] = x, boss[trans(i, j)] = trans(i, j), sz[trans(i, j)] = 1;
for(int i = 0; i < q; i++){
cin >> que[i].F >> que[i].S;
a[que[i].F][que[i].S] = 1;
}
for(int i = 1; i <= n; i++)
for(int j = 1; j <= m; j++)
if(!a[i][j]){
cnt++, mx = max(mx, 1);
for(int k = 0; k < 2; k++){
int nx = i + dx[k], ny = j + dy[k];
if(check(nx, ny))
combine(trans(i, j), trans(nx, ny));
}
}
for(int i = q - 1; i >= 0; i--){
ans[i] = make_pair(cnt, mx);
int x = que[i].F, y = que[i].S;
a[x][y] = 0;
cnt++;
for(int j = 0; j < 4; j++){
int nx = x + dx[j], ny = y + dy[j];
if(check(nx, ny))
combine(trans(x, y), trans(nx, ny));
}
mx = max(mx, sz[trans(x, y)]);
}
for(int i = 0; i < q; i++)
cout << ans[i].F << " " << ans[i].S << "\n";
}
```
:::
## [每個人都需要一個機器貓朋友](https://judge.csie.ncku.edu.tw/problems/98)
- Task Provider: NHDK Moon Festival Ten Point Round 2021
- Task Setter: raiso
### 首殺 team21_09 (02:40)
### 解法說明
首先,本題目是在找 區間和$\leq 0$ 的個數,如果直接枚舉 $L$ 與 $R$,時間複雜度為 $O(N^3)$。
使用前綴和加速計算區間和,時間複雜度是 $O(N^2)$。
考慮到一個問題,區間和就是兩個前綴和相減,也就是尋找兩個前綴和,前面的前綴和大於後面的前綴和時,此對應的區間和就是 $<0$,當我們將問題思考到這邊的時候,就完成了這題的要求。
前綴和的逆序數對數量就是解。
### 標準程式
:::spoiler 解法1
```cpp=
#include <bits/stdc++.h>
#define pb push_back
#define Int long long
using namespace std;
Int invpair(vector<Int> &v, int l, int r) {
if(l == r) return 0;
int m = (l+r)/2;
Int res = invpair(v, l, m) + invpair(v, m+1, r);
vector<Int> tmp;
for(int i = l, j = m+1; i <= m || j <= r; ) {
if(i <= m && (j > r || v[i] <= v[j])) {
tmp.pb(v[i++]);
res += (j-m-1);
}
else tmp.pb(v[j++]);
}
for(auto it: tmp) v[l] = it, l++;
return res;
}
void solve(){
int n;
cin >> n;
vector<Int> A(n), B{0};
for(auto& it: A) cin >> it;
for(auto it: A) B.pb(B.back()+it); //len(B) == n+1
Int ans = invpair(B, 0, n); //[L, R]
cout << ans << endl;
}
int main(){
ios::sync_with_stdio(0); cin.tie(0);
int t=1;
//cin >> t;
while(t--) solve();
return 0;
}
```
:::
:::spoiler 解法2
```cpp=
#include <bits/stdc++.h>
#define pb push_back
#define mp make_pair
#define add insert
#define Int long long
#define pi acosl(-1)
#define MEM(x) memset(x,0,sizeof(x))
#define x first
#define y second
using namespace std;
struct BIT {
vector<Int> A;
int n;
BIT(int _n) {
n=_n;
A.resize(n+1);
}
int lowbit(int idx) {
return (idx&(-idx));
}
void update(int idx, Int v) {
for(int i = idx+1; i <= n; i += lowbit(i)) A[i] += v;
}
Int query(int idx)
{
Int res = 0;
for(int i = idx+1; i; i -= lowbit(i)) res += A[i];
return res;
}
};
void solve(){
int n; cin >> n;
vector<Int> A(n), B{0}, Buni;
for(auto& it: A) cin >> it;
for(auto it: A) B.pb(B.back() + it);
Buni = B;
sort(Buni.begin(), Buni.end());
Buni.erase(unique(Buni.begin(), Buni.end()), Buni.end());
//BIT
BIT T1(Buni.size());
Int ans = 0;
for(int i = 0; i < B.size(); i++) {
int idx = lower_bound(Buni.begin(), Buni.end(), B[i]) - Buni.begin();
Int cnt = i - T1.query(idx);
ans += cnt;
T1.update(idx, 1LL);
}
cout << ans << endl;
}
int main(){
ios::sync_with_stdio(0); cin.tie(0);
int t=1;
//cin >> t;
while(t--) solve();
return 0;
}
```
:::