第一場比賽 題解
=
> 真材實料,百練自得!
# [z001](https://judge.cp.ccns.io/problem/z001):
重新解析題義,題目的意思就是 1. 找出整個數列裡最大的數,2. 找出此數在此數列中的最長連續出現次數。
特別要注意的是,本題會有許多人寫出可以 RE 的 code,通常是在讀數列的時候沒有擋好邊界,衝出宣告範圍。某個人就因為在 Codeforces 中四分鐘快速刷掉本題,結果賽後被 hack,因此很久都不打 Codeforces。
> 橘色的也一樣la ==
以下完整解題程式碼:
```cpp
#include <bits/stdc++.h>
using namespace std;
int main() {
int n, m, i, j, k;
cin >> n;
int d[100010];
int maxx = 0;
for (i = 0; i < n; i++) {
cin >> d[i];
maxx = max(maxx, d[i]);
}
int maxl = 0, cnt;
for (i = 0; i < n; i++) {
cnt = 0;
while (d[i] == maxx && i < n) {
cnt++;
i++;
}
maxl = max(maxl, cnt);
}
cout << maxl << endl;
return 0;
}
```
# [z002](https://judge.cp.ccns.io/problem/z002):
大家看完題目之後應該都可以很明顯的看出要把內容從大排到小,問題是方法,在本題中,我們設定了記憶體限制只有 10MB,計算過後可以發現,直接用 int 肯定是不會過的。
特別要注意的是,在某些比賽中,如果超過記憶體用量,也可能會給 RE 而不是 MLE,本題有兩種解題方法,其一,是盡可能減少變數的記憶體空間,使用 char 或 int8_t 儲存變數,並且 sort,不過這並不是很好,原因是輸入的數字最多有 $5\cdot 10^6$ 個,$n\log n$ 的解法會來到大概 $10^8$ 的複雜度,雖然就結果來說,是可以 AC 的,不過有更好的解法:counting sort。
counting sort 紀錄每個數字出現的次數,像本題,數字只會有 1 ~ 100,因此很適合 counting sort。
以下完整解題程式碼:
```cpp
#include <bits/stdc++.h>
using namespace std;
int n, tmp, i;
int cnt[110];
int main() {
cin >> n;
for (i = 0; i < n; i++) {
cin >> tmp;
cnt[tmp]++;
}
for (i = 100; i > 0; i--) {
while (cnt[i] --> 0) {
cout << i << " ";
}
}
cout << endl;
return 0;
}
```
# [z003](https://judge.cp.ccns.io/problem/z003):
當一開始看到這題的時候,我們會想要去模擬,但是看到他的範圍之後,粗估一下複雜度發現,模擬的複雜度太大,這時候會有兩個想法,`O(logN)` 或是 `數學解`,自此之後還是沒有想法,所以只好老老實實做實驗,把同一個`root`的數字全部找出來,這時候會發現他們都差`9`,到這裡這題就差不多了,只剩下實現這個數學解的想法。
```cpp
#include <bits/stdc++.h>
using namespace std;
int main() {
int n;
cin >> n;
while(n--) {
long long k, x;
cin >> k >> x;
cout << (k-1)*9+x << endl;;
}
return 0;
}
```
PS: 有興趣想要試試看如何做實驗搭配思考的同學可以試試看這題 [Codeforces-1110C](https://codeforces.com/contest/1110/problem/C),[參考解答](https://codeforces.com/contest/1110/submission/49595286)。
# [z004](https://judge.cp.ccns.io/problem/z004):
> 其實這題在第二週的練習題中有出 [name=ys]
很明顯的我們需要依序從**大到小**把傷害給挑出來按
問題是有個限制是:連續按超過 $k$ 次按鈕,就失敗
那麼就想看看如何在**不造成失敗**的情況下,依序從**大到小**把傷害給挑出來按
注意到**切換到不同**的按鈕之前,我們至多只能按 $k$ 次
當然是要按好按滿 :+1:
那將**連續相同**的按鈕,分為一組,從大到小將傷害給挑出來按,直到按了 $k$ 次就別按了
一組連續相同按鈕有個左界 $l$,接著找出右界 $r$ (採用左閉右開)
```cpp
while(r < n && s[l] == s[r]) r++;
```
而找到連續相同的一組,就將傷害排序,以方便從**大到小**把傷害給挑出來按
```cpp
sort(a+l, a+r);
```
然後累加前 $k$ 項
```cpp
sum = accumulate(a+max(l, r-k), a+r, sum);
```
要特別注意如果這連續按鈕**個數少於 $k$**,要做點處裡: `max(l, r-k)`
以下完整解題程式碼:
```cpp
#include<bits/stdc++.h>
using namespace std;
int const maxn = 2e5 + 100;
int n, k, a[maxn];
string s;
long long int sum = 0;
int main()
{
cin >> n >> k;
for(int i = 0; i < n; i++) cin >> a[i];
cin >> s;
int r = 0;
while(r < n) {
int l = r;
while(r < n && s[l] == s[r]) r++;
sort(a+l, a+r);
sum = accumulate(a+max(l, r-k), a+r, sum);
}
cout << sum << endl;
return 0;
}
```
# [z005](https://judge.cp.ccns.io/problem/z005):
模擬切格子的步驟就行了
從最高的地方 (`maxh`) 一路一層層往最低處 (`1`) 看
```cpp
for(int i = maxh; i >= 1; i--) {
:
.
}
```
對於每一層,收集**該層**的**格子**們 $s$,收集完存到累計的 `sum` 中
```cpp
sum += s;
```
但是當該層的格子加進 `sum` 中會超過 $k$ 時,就不能再收集了
此時**這個位置**就是要 slice 的地方
```cpp
if(sum + s > k) slice++, sum = 0;
```
而當 $s$ 等於 $n$ 時,也就是所有 tower 都**一樣高**了,就可以結束了
```cpp
if(s == n) {
if(sum) slice++;
break;
}
```
這裡特別注意 `sum` 如果還有東西,表示結束前還要切一刀 (slice)
在以上步驟之前,我們還得問,如何知道**某層**的 $s$ 為何?
```cpp
for(int i = 0; i < n; i++) scanf("%d", &h), s[h]++;
for(int i = maxh; i >= 1; i--) s[i-1] += s[i];
```
以下完整解題程式碼:
```cpp
#include<bits/stdc++.h>
using namespace std;
int const maxh = 2e5 + 10;
int n, k, s[maxh];
int main()
{
scanf("%d%d", &n, &k);
int h;
for(int i = 0; i < n; i++) scanf("%d", &h), s[h]++;
for(int i = maxh-1; i >= 1; i--) s[i-1] += s[i];
int sum = 0, ans = 0;
for(int i = maxh-1; i >= 1; i--) {
if(s[i] == n) {
if(sum) ans++;
break;
}
if(sum + s[i] > k) ans++, sum = 0;
sum += s[i];
}
printf("%d\n", ans);
return 0;
}
```