資料結構 習題講解
講師 王政祺
ds11
簡短題敘
給定四則運算式,計算其答案。
Python 作法
-
這邊先提供各位 python 的作法,如果真的在考試遇到了記得把語言切換一下
print(eval(input()))
Python 作法
-
這邊先提供各位 python 的作法,如果真的在考試遇到了記得把語言切換一下
-
C++ 比較笨,所以我們要研究 eval() 是怎麼運作的
print(eval(input()))
和上課例題有何不同?
- 比上課例題多了乘除,直接照著做會爛掉
和上課例題有何不同?
-
比上課例題多了乘除,直接照著做會爛掉
-
Why?
和上課例題有何不同?
-
比上課例題多了乘除,直接照著做會爛掉
-
Why?乘除的優先度比加減法高
和上課例題有何不同?
-
比上課例題多了乘除,直接照著做會爛掉
-
Why?乘除的優先度比加減法高
-
遇到 '(' 往回推的時候要另外處理
考慮一個沒有括弧的運算式
-
因為乘除比加減優先,所以先把乘除的答案算出來!
考慮一個沒有括弧的運算式
-
因為乘除比加減優先,所以先把乘除的答案算出來!
-
過程中如果遇到還不想處理的加減符號,就丟入 queue 中
考慮一個沒有括弧的運算式
-
因為乘除比加減優先,所以先把乘除的答案算出來!
-
過程中如果遇到還不想處理的加減符號,就丟入 queue 中
-
乘除的運算都結束以後,再從 queue 慢慢將加減的結果算出來
圖像說明
-
兩個 queue 分別紀錄符號和數值
-
一邊從 stack 中 pop,一邊 push 進 queue
11 + 5 * 3 - 6 / 2
圖像說明
-
11 後面接的是加減運算,所以丟入 queue
+ |
---|
11 |
---|
11 + 5 * 3 - 6 / 2
圖像說明
-
5 後面接的是乘除運算,因此可以直接計算
+ |
---|
11 |
---|
11 + 5 * 3 - 6 / 2
圖像說明
-
15 後面接的是加減運算,所以丟入 queue
+ | - |
---|
11 | 15 |
---|
11 + 15 - 6 / 2
圖像說明
-
6 後面接的是乘除運算,因此可以直接計算
+ | - |
---|
11 | 15 |
---|
11 + 15 - 6 / 2
圖像說明
-
這時候開始從 queue 一邊 pop 一邊計算
+ | - |
---|
11 | 15 | 3 |
---|
11 + 15 - 3
圖像說明
-
這時候開始從 queue 一邊 pop 一邊計算
+ | - |
---|
11 | 15 | 3 |
---|
11 + 15 - 3
圖像說明
-
這時候開始從 queue 一邊 pop 一邊計算
- |
---|
26 | 3 |
---|
26 - 3
圖像說明
-
將最後的結果丟回 stack!
23 |
---|
23
程式碼講解
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 200000;
template <typename T>
struct Stack{
T arr[MAXN];
int now;
Stack() : now(0) {}
T top() {
return arr[now-1];
}
void pop() {
now--;
}
void push(T val) {
arr[now++] = val;
}
T size() {
return now;
}
};
template <typename T>
struct Queue{
T arr[MAXN];
int head, tail;
Queue() : head(0), tail(0) {}
int front() {
return arr[head];
}
void pop() {
head++;
if (head == MAXN) head = 0;
}
void push(int val) {
arr[tail++] = val;
if (tail == MAXN) tail = 0;
}
int size() {
return (tail + MAXN - head) % MAXN;
}
};
string str;
int num, k=1;
Stack <int> stk_num; // 代表數字的 stack
Stack <char> stk_sym; // 代表運算符號的 stack
Queue <int> que_num; // 代表數字的 queue
Queue <char> que_sym; // 代表運算符號的 queue
void calc() {
int a = stk_num.top(), b;
stk_num.pop();
char op;
while (stk_sym.size() && stk_sym.top() != ')') {
b = stk_num.top();
stk_num.pop();
op = stk_sym.top();
stk_sym.pop();
if (op == '+' || op == '-') { // 遇到加減運算先不處理
que_sym.push(op);
que_num.push(a);
a = b;
} else if (op == '*') {
a = a * b;
} else if (op == '/') {
a = a / b;
}
}
que_num.push(a);
// 這邊回來解決加減運算
a = que_num.front();
que_num.pop();
while (que_sym.size()) {
b = que_num.front();
que_num.pop();
op = que_sym.front();
que_sym.pop();
if (op == '+') {
a = a + b;
} else if (op == '-') {
a = a - b;
}
}
if (stk_sym.size()) stk_sym.pop(); // 若並非結尾,則將 ')' pop 出來
stk_num.push(a); // 將運算結果放回 stack 中
}
signed main() {
cin >> str;
for (int i = str.size()-1; i >= 0; i--) { // 由後往前!
if (str[i] >= '0' && str[i] <= '9') {
num = num + k * (str[i] - '0');
if (i-1 >= 0 && str[i-1] >= '0' && str[i-1] <= '9') {
k = k * 10; // 如果下一位還是數字的話
} else {
stk_num.push(num);
num = 0, k = 1;
}
} else if (str[i] != '(') {
stk_sym.push(str[i]); // 遇到正常運算符號就 push 進 stack
} else if (str[i] == '(') {
calc(); // 遇到 '(' 就回推
}
}
calc(); // 結尾回推
cout << stk_num.top() << '\n';
return 0;
}
ds13
簡短題敘
給定長度為 \(N\) 的序列,問每個長度 \(K\) 連續區間的區間最大值。
(\(N, K \leq 10^6\))
直覺的作法
-
\(\mathcal{O}(NK)\),只要枚舉每段區間尋找最大值就行了。
直覺的作法
-
\(\mathcal{O}(NK)\),只要枚舉每段區間尋找最大值就行了。
-
但這樣不會過
直覺的作法
-
\(\mathcal{O}(NK)\),只要枚舉每段區間尋找最大值就行了。
-
但這樣不會過
-
我們對這題期望的複雜度是 \(\mathcal{O}(N)\)
如果從左掃到右呢?
-
每次維護一段長度為 \(K\) 之連續區間的答案
如果從左掃到右呢?
-
每次維護一段長度為 \(K\) 之連續區間的答案
-
往右挪一格的時候:
如果從左掃到右呢?
-
每次維護一段長度為 \(K\) 之連續區間的答案
-
往右挪一格的時候:
-
右邊新加入一個數字,拿來跟原本取 max
-
如果從左掃到右呢?
-
每次維護一段長度為 \(K\) 之連續區間的答案
-
往右挪一格的時候:
-
右邊新加入一個數字,拿來跟原本取 max
-
但左邊要怎麼刪掉?
-
如果從左掃到右呢?
-
每次維護一段長度為 \(K\) 之連續區間的答案
-
往右挪一格的時候:
-
右邊新加入一個數字,拿來跟原本取 max
-
但左邊要怎麼刪掉?答案是沒辦法
-
還記得「單調性」嗎?
-
回想一下,要怎麼找到每一項左邊第一個比自己小的數值?
還記得「單調性」嗎?
-
考慮每個數值的作用範圍(有機會被當成最大值的範圍)
這次要維護的是單調遞減!
-
如果一個數值的右邊出現了比他還要大的數值,那麼該數值將不再可能成為答案。
這次要維護的是單調遞減!
-
如果一個數值的右邊出現了比他還要大的數值,那麼該數值將不再可能成為答案。
-
用 deque 維護一段區間中到該查詢點(也就是右界)為止遞減的數字(每個數字右邊都沒有比他大的數字)
這次要維護的是單調遞減!
-
如果一個數值的右邊出現了比他還要大的數值,那麼該數值將不再可能成為答案。
-
用 deque 維護一段區間中到該查詢點(也就是右界)為止遞減的數字(每個數字右邊都沒有比他大的數字)
-
Why deque?
每個數值的作用範圍有額外限制
-
我們要找的是「一段區間」的最大值
每個數值的作用範圍有額外限制
-
我們要找的是「一段區間」的最大值
-
隨時檢查 deque 的 front 有沒有超過範圍限制
-
deque 才能 pop_front()
-
那答案呢?
-
每次處理完後,deque 的 front 就是那段區間的答案!
時間複雜度
-
每個數值恰好只會被加入 deque 、從 deque 中刪除各一次,因此均攤複雜度為 \(\mathcal{O}(N)\)
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1000000;
struct Deque{
int arr[MAXN], head, tail;
Deque() : head(0), tail(0) {}
int front() {
return arr[head];
}
int back() {
if (tail == 0) return arr[MAXN-1];
return arr[tail-1];
}
void pop_front() {
head++;
if (head == MAXN) head = 0;
}
void pop_back() {
if (tail == 0) tail = MAXN;
tail--;
}
void push_front(int val) {
if (head == 0) head = MAXN;
arr[--head] = val;
}
void push_back(int val) {
arr[tail++] = val;
if (tail == MAXN) tail = 0;
}
int size() {
return (tail + MAXN - head) % MAXN;
}
} deq;
int N, K;
int x[MAXN+1];
signed main() {
ios_base::sync_with_stdio(0), cin.tie(0);
cin >> N >> K;
for (int i = 1; i <= N; i++) {
cin >> x[i];
}
// 先處理前 K-1 個
for (int i = 1; i <= K-1; i++) {
while (deq.size() && x[i] >= x[deq.back()]) {
deq.pop_back();
}
deq.push_back(i);
}
for (int i = K; i <= N; i++) {
if (deq.size() && i - deq.front() >= K) deq.pop_front();
// 如果距離和自己超過 K-1 就代表不在這次區間的考慮範圍
while (deq.size() && x[i] >= x[deq.back()]) {
deq.pop_back();
}
deq.push_back(i);
cout << x[deq.front()] << " \n"[i==N];
// 當前的 front() 即是這段區間的最大值
}
return 0;
}
ds25
簡短題敘
給定長度為 \(L\) 的線段,接下來有 \(N\) 次操作,每次選定一個位置 \(x_i\) 切下去將涵蓋該位置之線段分為兩段,求每次所切之線段長度的和。
(\(N \leq 2 \cdot 10^5, L \leq 10^7\))
目標
-
其實就是維護每段連續的線段,每次切的時候要知道切在哪
如何達成
-
我們可以用 set 裡頭存 pair 這樣的結構來達成目標
如何達成
-
我們可以用 set 裡頭存 pair 這樣的結構來達成目標
-
每個 pair 代表的就是一個線段的左右界
如何達成
-
我們可以用 set 裡頭存 pair 這樣的結構來達成目標
-
每個 pair 代表的就是一個線段的左右界
-
每次要切的時候,用 lower_bound 找到第一個左界比自己小的 pair(也就是會切到的線段)
如何達成
-
我們可以用 set 裡頭存 pair 這樣的結構來達成目標
-
每個 pair 代表的就是一個線段的左右界
-
每次要切的時候,用 lower_bound 找到第一個左界比自己小的 pair(也就是會切到的線段)
-
將長度加入答案後 erase,然後再 insert 切出來的兩段
如何達成
-
我們可以用 set 裡頭存 pair 這樣的結構來達成目標
-
每個 pair 代表的就是一個線段的左右界
-
每次要切的時候,用 lower_bound 找到第一個左界比自己小的 pair(也就是會切到的線段)
-
將長度加入答案後 erase,然後再 insert 切出來的兩段
-
AC~
#include <bits/stdc++.h>
#define pii pair<int,int>
#define ff first
#define ss second
using namespace std;
const int MAXN = 200000;
const int INF = 1e9;
int N, L;
int x, k;
int arr[MAXN+1];
long long ans;
set <pii> S;
signed main() {
cin >> N >> L;
S.insert(pii(0, L));
for (int i = 1; i <= N; i++) {
cin >> x >> k;
arr[k] = x;
}
for (int i = 1; i <= N; i++) {
pii pr = *prev(S.lower_bound(pii(arr[i], INF)));
ans += pr.ss - pr.ff;
if (arr[i] == 0 || arr[i] == L) continue;
S.erase(pr);
S.insert(pii(pr.ff, arr[i]));
S.insert(pii(arr[i], pr.ss));
}
cout << ans << '\n';
return 0;
}
The End
APCS Camp 資料結構 題解
By CasperWang
APCS Camp 資料結構 題解
APCS Camp 資料結構
- 440