非官方題解 by 晴 | 2023 建北電資
1
3
2
@fatman (巧虎)
\(400\) Scores
\(17\) Submissions
@yitien (信田)
\(200\) Scores
\(7\) Submissions
@fishball (魚丸)
\(150\) Scores
\(3\) Submissions
海平面的大氣壓力為 \(x\) hPa
每上升 \(1\) 千公尺大氣壓力會降低 \(y\) hPa
海拔為 \(z\) 千公尺時,大氣壓力為多少 hPa?
\(x - y \times z\)
還能有什麼思路
這就是俗稱的「簽到題」,有來寫的話大部分人都可以 AC 的
#include <iostream>
int main() {
// 起始值,削減速度,為期
int initialValue, reducingSpeed, duration;
std::cin >> initialValue >> reducingSpeed >> duration;
std::cout << initialValue - reducingSpeed * duration << "\n";
}
給你 \(n\) 個字串,請依下列規則產生一個神奇字串:
假如第 \(k\) 個字串的第一個字元為 'a'
則取此字串的第 \(1\) 個字元
假如第 \(k\) 個字串的第一個字元為 'b'
則取此字串的第 \(2\) 個字元
'c' 則是 \(3\);'d' 則是 \(4\),以此類推
若字串的長度不足,則取空格
第一行有一個數字 \(n\)
第二行會有 \(n\) 個字串
輸出依此規則得到的那個字串
大家知道字元在電腦中其實是存 ASCII 碼,而且 a ~ z 是依序排列的
所以可以直接用 x - 'a' 來獲取 x 是第幾個英文字母
(Zero-based Numbering 的好處就出現了!)
| 字元 | 'a' | 'b' | 'c | 'd' | 'e' | 'f' | 'g' |
| 號碼 | 97 | 98 | 99 | 100 | 101 | 102 | 103 |
與 'a' 的差距 |
0 | 1 | 2 | 3 | 4 | 5 | 6 |
#include <iostream>
int main() {
// 會有幾個字串
int numStrings;
std::cin >> numStrings;
// 用來佔存的字串
std::string str;
for (int i = 0; i < numStrings; ++i) {
std::cin >> str;
// str 第 0 個字元與 'a' 的距離 = 要輸出第幾個字
int index = str[0] - 'a';
// 如果在字串長度範圍內
if (index < str.length()) {
// 直接輸出那個字元即可
std::cout << str[index];
}
else {
// 超過長度,輸出空格
std::cout << ' ';
}
}
}
給你 \(n\) 個不同面額的金幣,如 \([\ 1,\ 3,\ -2,\ 5,\ -3\ ]\)
你可以自由切割成幾個小區塊,如 \([\ 1,\ 3\ ]\ [\ -2,\ 5 \ ]\ [\ -3\ ]\)
試求
它們乘上他們由左至右順序,如 \(1\) \(\cdot [1 + 3] +\) \(2\) \(\cdot [(-2) + 5] +\) \(3\) \(\cdot [-3] = -1\)
的最大值
※ 例題的結果最大值為切割成 \([\ 1\ ]\ [\ 3,\ -2 \ ]\ [\ 5,\ -3\ ]\),是 \(9\)
第一行會有兩個數字,\(n\) 和 \(k\)
第二行會有 \(n\) 個數字,分別是每個金幣的面額
請輸出依照題意算出的結果是否 \(> k\)
是 = 輸出 "safewilly",否 = 輸出 "willygonemad"
要怎麼做?
要怎麼做?
要怎麼做?
要怎麼做?
要怎麼做?
要怎麼做?
後綴和!
現在我們知道可以用後綴和表示切割後的結果,但有什麼用呢?
→ 如何決定要切在哪
| 值 | 1 | 3 | -2 | 5 | -3 |
| 後綴和 | 4 | 3 | 0 | 2 | -3 |
| 值 | 1 | 3 | -2 | 5 | -3 |
| 後綴和 | 4 | 3 | 0 | 2 | -3 |
| -3 |
| 2 |
| 0 |
| 3 |
| 4 |
| 值 | 1 | 3 | -2 | 5 | -3 |
| 後綴和 | 4 | 3 | 0 | 2 | -3 |
| -3 |
| 2 |
| 0 |
| 3 |
| 4 |
Greedy,只要 > 0 的就要切
| 值 | 1 | 3 | -2 | 5 | -3 |
| 後綴和 | 4 | 3 | 0 | 2 | -3 |
不過還有一個問題
不過還有一個問題
| 值 | -8 | 3 | -2 | 5 | -3 |
| 後綴和 | -5 | 3 | 0 | 2 | -3 |
假設它的後綴和第 0 項是負的
我們可以不拿嗎?
| 值 | -8 | 3 | -2 | 5 | -3 |
| 後綴和 | -5 | 3 | 0 | 2 | -3 |
我們可以不拿嗎?
| 2 |
| 3 |
| 值 | -8 | 3 | -2 | 5 | -3 |
| 後綴和 | -5 | 3 | 0 | 2 | -3 |
我們可以不拿嗎?
| 2 |
| 3 |
一定得拿!
| -5 |
#include <iostream>
#include <vector>
int main() {
// 有多少金幣面額要輸入,最後要比較的門檻值
long long numCoins, expectedValue;
std::cin >> numCoins >> expectedValue;
// 輸入進陣列
std::vector<long long> coins(numCoins);
for (long long i = 0; i < numCoins; ++i) {
std::cin >> coins[i];
}
// 用同一個 vector 來存後綴和,只是改名叫 suffixSum 以免混淆
#define suffixSum coins
// 建立後綴和陣列
for (long long i = numCoins - 2; i >= 0; --i) {
suffixSum[i] += suffixSum[i + 1];
}
// 第 0 項一定得拿
long long result = suffixSum[0];
// 其他只拿 > 0 的
for (long long i = 1; i < numCoins; ++i) {
if (suffixSum[i] > 0)
result += suffixSum[i];
}
// 依照題目要求輸出
if (result >= expectedValue) {
std::cout << "safewilly\n";
}
else {
std::cout << "willygonemad\n";
}
}真的需要把後綴和存起來嗎?
→ 邊建立邊判斷要不要拿
// 用一個變數 currentSuffixSum 紀錄目前的後綴和值
long long currentSuffixSum = 0, result = 0;
for (long long i = numCoins - 1; i >= 0; --i) {
currentSuffixSum += coins[i];
// 若 > 0 或者為第 0 項,則要拿
if (currentSuffixSum > 0 || i == 0)
result += currentSuffixSum;
}#include <iostream>
#include <vector>
int main() {
// 有多少數字要輸入,最後要比較的門檻值
long long numCoins, expectedValue;
std::cin >> numCoins >> expectedValue;
// 輸入進陣列
std::vector<long long> coins(numCoins);
for (long long i = 0; i < numCoins; ++i) {
std::cin >> coins[i];
}
// 用一個變數 currentSuffixSum 紀錄目前的後綴和值
long long currentSuffixSum = 0, result = 0;
for (long long i = numCoins - 1; i >= 0; --i) {
currentSuffixSum += coins[i];
// 若 > 0 或者為第一項,則要拿
if (currentSuffixSum > 0 || i == 0)
result += currentSuffixSum;
}
// 依照題目要求輸出
if (result >= expectedValue) {
std::cout << "safewilly\n";
}
else {
std::cout << "willygonemad\n";
}
}請維護一個資料結構,支援以下功能:
首先輸入一個數字 \(Q\) 代表接下來有多少次操作
接著 \(Q\) 行可能有這三種指令:
1 X 代表加入 \(x\) 這個數字2 0 代表輸出最小值並刪除它2 1 代表輸出最大值並刪除它Priority Deque 這個名字只是在搞你,它事實上就是 Multiset
在 C++ 中,Multiset 是標準範本庫 STL 中的一個容器,它是一個有序的關聯容器,允許儲存相同的元素。與 Set 不同的是,Multiset 允許儲存多個相同的元素,而 Set 只能儲存唯一的元素。
Multiset 以紅黑樹(Red-black Tree)的資料結構實現,這確保了元素的有序性。紅黑樹是一種自平衡的二元搜尋樹,可以保持元素按照某個特定的排序準則有序儲存。根據 ChatGPT:
#include <iostream>
#include <set>
// 定義一個自己的資料結構
struct PriorityDeque {
std::multiset<long long> set;
void insert(long long value) {
set.insert(value);
}
long long popMin() {
// begin 是一個 iterator,用 * 取值
int smallest = *set.begin();
// 刪除它
set.erase(set.begin());
// 回傳
return smallest;
};
long long popMax() {
// end 是右界,左閉右開所以要 -- 才會是最後那一項的位置
int largest = *(--set.end());
set.erase(--set.end());
return largest;
}
};
int main() {
// 宣告一個我們自己定義的 struct
PriorityDeque pd;
// 有多少指令
int numActions;
std::cin >> numActions;
for (int i = 0; i < numActions; ++i) {
// 指令的第一部分,1 是插入值,2 是刪除值
char action;
std::cin >> action;
switch (action) {
case '1':
// 輸入要插入的值
long long value;
std::cin >> value;
// 插入
pd.insert(value);
break;
case '2':
// 指令的第二部分,0 是針對最小值,1 則是最大值
char symbol;
std::cin >> symbol;
if (symbol == '0') {
std::cout << pd.popMin() << "\n";
}
else {
std::cout << pd.popMax() << "\n";
}
break;
}
}
}
你有一些繁忙的時段,時段內不能參與任何活動
接著有一些活動時段(活動時段之間不會重疊)
求你最多可以參與多少活動?
這題目的輸入格式超噁心
第一行是一個數字 \(n\) 代表有幾個繁忙時段
接下來一行是這 \(n\) 個繁忙時段的開始時刻
第三行是這 \(n\) 個繁忙時段的持續時長
第四行是一個數字 \(m\) 代表有幾個活動時段
接下來 \(m\) 行會有 \(s_i\) \(e_i\) 分別代表一個活動時段的開始與結束時間
其實這題很暴力
就是跌代,O(\(nm\)) 解決
Busy
Event
Busy
Event
其實這題很暴力
就是跌代,O(\(nm\)) 解決
Busy
Event
其實這題很暴力
就是跌代,O(\(nm\)) 解決
Busy
Event
其實這題很暴力
就是跌代,O(\(nm\)) 解決
Busy
Event
其實這題很暴力
就是跌代,O(\(nm\)) 解決
Busy
Event
答:共能出席 1 場活動
其實這題很暴力
就是跌代,O(\(nm\)) 解決
我比較喜歡使用 iterator 來跌代 vector
順便解釋一下它的用法好了
vector.begin() 會回傳一個 iterator,指向陣列的開始
vector.end() 則是指向右界(最後一項的下一項,因為左閉右開)
使用 it++ 可以讓它指向下一個
使用 it-- 可以讓它指向上一個
使用 *it 會回傳它指向的那一項的值,但記得要加括號
#include <iostream>
#include <vector>
#include <algorithm>
// 定義一個結構代表一段時間,紀錄開始與結束
struct Period {
long long start;
long long end;
Period(long long start = -1, long long end = -1) : start(start), end(end) {}
};
// 給 std::sort 看的比較運算子
// 輸入兩個 Period,回傳左邊是否應該被整理到右邊的前面
bool operator < (Period left, Period right) {
// 左邊先開始,一定放前面
if (left.start < right.start) {
return true;
}
// 同時開始
else if (left.start == right.start) {
// 看哪邊先結束
return left.end < right.end;
}
// 右邊先開始
else {
return false;
}
}
int main() {
// 這題得加 IO 優化否則會 TLE
std::ios_base::sync_with_stdio(false);
std::cin.tie(0);
// 輸入有幾個繁忙時段
long long numBusies;
std::cin >> numBusies;
// 根據詭異的規則輸入 Busy Periods
std::vector<Period> busyPeriods(numBusies);
for (auto& b : busyPeriods)
std::cin >> b.start;
for (auto& b : busyPeriods) {
long long duration;
std::cin >> duration;
b.end = b.start + duration;
}
// 輸入有幾個活動時段
long long numEvents;
std::cin >> numEvents;
// 輸入 Event Periods
std::vector<Period> eventPeriods(numEvents);
for (auto& e : eventPeriods)
std::cin >> e.start >> e.end;
// 整理兩個陣列
std::sort(busyPeriods.begin(), busyPeriods.end());
std::sort(eventPeriods.begin(), eventPeriods.end());
long long result = 0;
// iterator 名字太長了,我懶得打
using Itr = std::vector<Period>::iterator;
// 把每個 Event Period 都跑過一遍,並確認有沒有重疊到任何 Busy Period
for (Itr ep = eventPeriods.begin(); ep != eventPeriods.end(); ++ep) {
// 從最開頭檢查
Itr bp = busyPeriods.begin();
// 忽略所有在這個 Event Period 開始之前就已經結束的 Busy Period
// 別忘了先判斷是不是整個陣列都走完了,否則
// 有可能會 無窮迴圈 / 跌代超出右界
while (bp != busyPeriods.end() && (*bp).end < (*ep).start)
++bp;
/* 如果已經把整個 Busy Period 陣列跑過一遍,代表
已經沒有其他 Busy Period 了,於是便符合條件
或者
下一個 Busy Period 的開始時間已經
超過現在這個 Event Period 的結束時間,因為
陣列是排好序的,所以後面全部的 Busy Period 都不可能會重疊 */
if (bp == busyPeriods.end() || (*bp).start > (*ep).end)
result++;
}
std::cout << result << "\n";
}
首先,我們只是要存開始和結束時間
似乎不需要用自己的 struct,用 pair 應該就可以了
這樣也不用自己定義用來 sort 的比較運算子
然後,重點來了,真的有必要每次都重頭判斷每一個 Busy Period 嗎?
→ 已經在上一個活動時段開始之前就結束的繁忙時段
不可能會重疊到現在這個活動時段!
→ 每次迴圈不用重置 bp
#include <iostream>
#include <vector>
#include <algorithm>
#include <utility>
// 改用 pair 取代 Period
// 但是寫 pair、first 和 second 很不直覺,改掉
#define Period std::pair<long long, long long>
#define startTime first
#define endTime second
int main() {
// 這題得加 IO 優化否則會 TLE
std::ios_base::sync_with_stdio(false);
std::cin.tie(0);
// 輸入有幾個繁忙時段
long long numBusies;
std::cin >> numBusies;
// 根據詭異的規則輸入 Busy Periods
std::vector<Period> busyPeriods(numBusies);
for (auto& b : busyPeriods)
std::cin >> b.startTime;
for (auto& b : busyPeriods) {
long long duration;
std::cin >> duration;
b.endTime = b.startTime + duration;
}
// 輸入有幾個活動時段
long long numEvents;
std::cin >> numEvents;
// 輸入 Event Periods
std::vector<Period> eventPeriods(numEvents);
for (auto& e : eventPeriods)
std::cin >> e.startTime >> e.endTime;
// 整理兩個陣列
std::sort(busyPeriods.begin(), busyPeriods.end());
std::sort(eventPeriods.begin(), eventPeriods.end());
long long result = 0;
// iterator 名字太長了,我懶得打
using Itr = std::vector<Period>::iterator;
// 一開始從頭,後來就不用重置了
Itr bp = busyPeriods.begin();
// 把每個 Event Period 都跑過一遍,並確認有沒有重疊到任何 Busy Period
for (Itr ep = eventPeriods.begin(); ep != eventPeriods.end(); ++ep) {
// 忽略所有在這個 Event Period 開始之前就已經結束的 Busy Period
// 別忘了先判斷是不是整個陣列都走完了,否則
// 有可能會 無窮迴圈 / 跌代超出右界
while (bp != busyPeriods.end() && (*bp).endTime < (*ep).startTime)
++bp;
/* 如果已經把整個 Busy Period 陣列跑過一遍,代表
已經沒有其他 Busy Period 了,於是便符合條件
或者
下一個 Busy Period 的開始時間已經
超過現在這個 Event Period 的結束時間,因為
陣列是排好序的,所以後面全部的 Busy Period 都不可能會重疊 */
if (bp == busyPeriods.end() || (*bp).startTime > (*ep).endTime)
result++;
}
std::cout << result << "\n";
}
話說 有些題目是我先填好名字 他們才開始想題目內容的
這次大社賽題目其實不是特別困難
我覺得最有意義的就是 pC 雖然它題序一堆廢話
認真說就是 cjtsai 在搞你們啦
然後其實是因為官方的題解我看不懂…… 所以我才做這份簡報的 :D