【C++】競程筆記(資料結構:Unordered set / map)
===
[TOC]
程式碼範例參考:NTUCPC Guide,此筆記僅為個人學習用途。
簡介
---
Unordered set / map 皆為無序的(unordered)關聯式容器,內部實作原理皆是 Hash(雜湊),對於每次操作的時間複雜度都是 $O(1)$ 。如果此資料結構發生碰撞的話,最壞的時間複雜度會到 $O(n)$ 。
那既然都無序了,所以就字面上意思,輸出的元素不一定會照著順序排。
Unordered set / map 提供了較快的操作速度,在操作上面也與原本的 set / map 無異,基本上都相通。
另外兩者的標頭檔都有稍微更改:
```cpp=
#include <unordered_set>
#include <unordered_map>
```
### 雜湊(Hash)介紹
> 雜湊函式(英語:Hash function)又稱雜湊演算法,是一種從任何一種資料中建立小的數字「指紋」的方法。雜湊函式把訊息或資料計算成摘要,使得資料量變小,將資料的格式固定下來。該函式將資料打亂混合,重新建立一個叫做雜湊值(又叫雜湊值)(hash values,hash codes,hash sums,或hashes)的指紋。
> From Wikipedia
詳細參考:https://ithelp.ithome.com.tw/articles/10208884
習題練習
---
### Sum of Four Values
Source:https://cses.fi/problemset/task/1642
解題思路:
要找出四個索引 $i, j, k, l$ ,使得: $$a[i]+a[j]+a[k]+a[l]=x$$
- $i, j, k, l$ 必不同。
- 任意順序輸出皆可。
如果用暴力解會是 $O(n^4)$ ,非常的可怕。
所以這邊用一個 Two Sum 的思路去解,就是兩數和+兩數和=四數和。
可以先把所有兩數和的組合及其對應的 index 存進一個表(hash table),然後遍歷所有可能的兩數組合,查詢是否存在另一個兩數組合,使總和為 x,並且四個 index 互不重複。
解題步驟:
1. 建立 `unordered_map<long long, vector<pair<int,int>>>`:
- Key 為 $a[i] + a[j]$ 。
- Value 是所有產生此和的 $(i, j)$ 索引對。
2. 再次列舉所有兩數組 $(k, l)$ :
- 查找 map 中是否有 $x - (a[k] + a[l])$ 的和存在。
- 確保這四個索引互不相同。
第 29 行的 `long long target = x - 1LL * a[i] - a[j];`:
是為了用兩數和+兩數和=四數和的方法,原本的公式長成第一式那樣,第二式只是將 `a[k] + a[l]` 移項所得而已。 $$a[i] + a[j] + a[k] + a[l] = x$$
$$a[k] + a[l] = x - (a[i] + a[j])$$
程式碼:
```cpp=
#include <bits/stdc++.h>
using namespace std;
using pii = pair<int, int>;
int main() {
int n;
long long x;
cin >> n >> x;
vector<int> a(n);
for (int &num : a) cin >> num;
// unordered_map 儲存所有 a[i] + a[j] 的組合與對應索引
unordered_map<long long, vector<pii>> sum_map;
// 列舉所有兩數和(把他們都加進去 unordered_map)
for (int i = 0; i < n; ++i) {
for (int j = i + 1; j < n; ++j) {
// 1LL 轉成 long long
long long sum = 1LL * a[i] + a[j];
sum_map[sum].emplace_back(i, j); // 存入對應索引
}
}
// 再次列舉所有兩數,查找對應的 target sum
for (int i = 0; i < n; ++i) {
for (int j = i + 1; j < n; ++j) {
long long target = x - 1LL * a[i] - a[j];
if (sum_map.find(target) != sum_map.end()) {
for (auto &[k, l] : sum_map[target]) {
// 確保四個索引互不相同
if (i != k && i != l && j != k && j != l) {
cout << i + 1 << " " << j + 1 << " " << k + 1 << " " << l + 1 << "\n";
return 0;
}
}
}
}
}
cout << "IMPOSSIBLE\n";
return 0;
}
```