作業六題解
===
本週的主題是Greedy Algorithm,以下是[俄羅斯娃娃](https://judge.ccns.io/problems/17)與[青娃過河](https://judge.ccns.io/problems/18)的題解。
## [俄羅斯娃娃](https://judge.ccns.io/problems/17)
- Task Provider: UVA 11100
- Task Setter: MinHsuan
### 解法說明
這題出的不太好,因過度簡化題目導致不需要使用Greedy也很輕易就能得到答案,先跟大家道個歉。題目上應該增加一個條件,**請盡可能將每個娃娃的層數是最少的**,以下就已加入條件後的題目做講解。
這題的想法非常簡單,可以分為兩個關鍵部分
1. 得到娃娃的集合數量(即有幾組娃娃)
- 每組俄羅斯娃娃的大小會符合 X~i-1~ < X~i~ < X~i+1~,因此出現最多次的數字,至少會有他出現次數的組數。
2. 平均分配娃娃讓每組娃娃層數盡可能少
- 代表要讓集合內元素盡量都是最少的,因此我們可以平均分配給娃娃到集合內。
有了上述的想法,先設定總共有 $m$ 個數字,首先將陣列排序,接著找到出現次數最多的數字,假定他的次數是 $n$ ,間隔 $n$ 個數字取出數字放入集合,完成第一個集合,依序執行 ${m \over n}$ 次後,即可得到所有集合內的內容。
有些人可能會覺得困惑,這樣會不會有同個集合內有兩個一樣的數字呢?
不用擔心,因為一個數字最多出現 n 次,因此間隔 n 個數字即可避免取到同一個數字。
### 參考解答
提供一個簡單的解答,直接排序後,計算出現最多次的數字,並且輸出即可。
本解答適用於新增條件後的題目,也適用於原題。
:::spoiler
```cpp=
#include <iostream>
#include <algorithm>
#include <unordered_map>
using namespace std;
int d[1001], N, n;
unordered_map <int, int> m; //存放數字出現次數
int main(){
cin >> N;
n = N;
while(N--)
cin>> d[N];
for(auto i=0;i<n;i++){ //統計數字出現次數
if(m.find(d[i])==m.end())
m[d[i]] = 1;
else m[d[i]]++;
}
int interval = 0;
for(auto i=m.begin();i!=m.end();i++) //找出最大的出現次數
interval = max(interval, i->second);
sort(d, d + n); //排序陣列
for(auto i=0;i<interval;i++){ //依據間隔輸出答案
for(auto j=i;j<n;j+=interval)
cout << d[j] << " ";
cout << endl;
}
return 0;
}
```
:::
## [青蛙過河](https://judge.ccns.io/problems/18)
- Task Provider: UVA 11157
- Task Setter: MinHsuan
### 解法說明
題目提到可能有大石頭與小石頭,大石頭可以無限次使用,小石頭只能夠使用一次。大石頭的部分很簡單,只要下一塊最近的石頭是大石頭,不需要考慮直接選擇就行,小石頭的部分則需要考慮石頭使用的分配,假設某塊石頭去程先用掉,回程則無法使用,某些狀況下回程會需要這塊石頭來達成最小距離。
我們可以想想,回程其實也只是一些石頭位置的分配,分配好這些石頭後,不管向前跳向後跳都是一樣的,因此可以將題目轉換成兩隻青蛙往目的地跳,途中這兩隻青蛙共同計算每次跳的距離(說是兩隻青蛙,但都是小青本蛙)。這題Greedy的想法便是在這裡,每次遇見小石頭時,我們讓離石頭比較遠的青蛙跳過來(選近的青蛙的話,比較遠的青蛙下次又變得更遠了),這樣子兩隻青蛙輪流使用近的石頭,就不會有其中一隻越來越遠了,重複這樣的行為,便可以得到答案。
### 參考解答
:::spoiler
```cpp=
#include <iostream>
#include <deque>
#include <algorithm>
using namespace std;
deque<int> b, s;
int T, N, P;
int ans, pos_go, pos_back, r_size;
char rock;
int main(){
cin >> T;
while (T--){
cin >> N >> P;
b.clear();
s.clear();
for(int i=0;i<N;i++){
cin >> rock >> r_size;
if(rock == 'b')
b.push_back(r_size);
else if(rock == 's')
s.push_back(r_size);
}
ans = 0; //每次青蛙跳躍的時候,紀錄最遠的跳躍距離
pos_go = 0; //pos_go, pos_back紀錄目前青蛙的位置
pos_back = 0;
for(int i=0;i<N;i++){
if(s.empty() || !b.empty() && b.front() < s.front()){ //下一顆最近的是大石頭,一起跳上去
ans = max(ans, b.front()-pos_go);
pos_go = b.front();
ans = max(ans, b.front()-pos_back);
pos_back = b.front();
b.pop_front();
}
else{ //下一顆最近的是小石頭
if(pos_go < pos_back){ //看誰比較遠,遠的跳上去
ans = max(ans, s.front()-pos_go);
pos_go = s.front();
}
else{
ans = max(ans, s.front()-pos_back);
pos_back = s.front();
}
s.pop_front();
}
}
ans = max(ans, P-pos_go);
ans = max(ans, P-pos_back);
cout << ans <<endl;
}
}
```
:::