STL - 資料結構篇

建國中學 游承曦

什麼是STL? 能吃嗎?

  • 演算法
  • 資料結構
  • 函式
  • 迭代器 Iterator

C++ Standard Template Library

一定要加!!!

using namespace std;

簡單的資料結構

  • Stack 堆疊

  • Queue 佇列

  • Doubled-Ended Queue 雙向佇列

Animation from FHVirus Orz

Stack 堆疊

概念

基本操作

  • push(x) 將元素x放到Stack頂端

  • top() 查詢Stack頂端元素

  • pop() 將Stack頂端元素取出

【 Stack 操作都是先進後出 】

實作

const int N = 1e5;
struct Stack{
    int stack[N];
    int top = 0;
    void push(int x){
        if(top == N){
            cout << "Full!\n";
            return;
        }
        else{
            stack[top] = x;
            top++;
        }
    }
    int pop(){
        if(top == 0){
            cout << "Empty!\n";
            return -1;
        }
        else{
            top--;
            return stack[top];
        }
    }
} stk;

偷懶的東西 v.1

#include <stack>
int main()
{
    stack<int> st; // 宣告
    
    st.push(1);
    st.push(2);
    cout << st.top(); // 2
    st.pop();
    cout << st.top(); // 1
    st.pop();
    
    cout << st.size(); // 0
    cout << st.empty(); // 1
}

題目

ZJ b923 - Stack 堆疊的模板題

裸 Stack

UVa 514 / ZJ c123 - Rails

一開始有個車站如下圖,在右側有車廂編號\(1\) ~ \(n\) 依序進站。現在,我們想將火車重新編號為 \(a_1, a_2, ..., a_n\)出站,但先進站的車廂只能等後進站的車廂出站才能離開。給定\(n\)與\(a_1, a_2, ... a_n\),試問有沒有辦法達成此排列方式?

ZJ e924 - pC. 括號配對

給你一堆()[]<>{},判斷是否配對成功

成功例子: {()<([()])>}

失敗例子: ((){[>}{)

TIOJ 1176 - Cows

給定一排牛 (不是一牛排) 及每頭牛的高度,

牠們只能往右平視或俯視,

請問牠們分別能夠看到幾頭牛?
(如果兩頭牛的高度一樣,

那麼左邊的牛的視野只能看到右邊的牛為止)

\(N \leq 10^6\) ,顯然\(O(n^2)\)會爆

Hint : 維護單調性 (monotone)

ZJ c907 - 尋找最大矩形

(經典題) 給\(N\)個寬度為\(1\)的長條圖,

求其所形成的最大矩形面積

:跟上面那題很像,而且它還可以延伸

Queue 佇列

KYW

概念

不能從中間插隊

不能從中間出來

可以從前面離開

可以從後面加入排隊

基本操作

  • push(x) 將元素x放到Queue尾端

  • top() 查詢Queue前端元素

  • pop() 將Queue前端元素取出

【 Queue 操作都是先進先出 】

實作

const int N = 1e5;
struct Queue{
    int queue[N];
    int front = 0, back = 0;
    void push(int x){
        if(back == N){
            cout << "Error!\n";
            return;
        }
        else{
            queue[back] = x;
            back++;
        }
    }
    int pop(){
        if(front == back){
            cout << "Empty!\n";
            return -1;
        }
        else{
            int ret = queue[front];
            front++;
            return ret;
        }
    }
} qu;

但這種實作方式有空間有限性,

所以有另一種延伸的實作叫Cycling Queue

偷懶的東西 v.2

#include <queue>
int main()
{
    queue<int> qu; // 宣告
    
    qu.push(1);
    qu.push(2);
    cout << qu.front(); // 1
    qu.pop();
    cout << qu.front(); // 2
    
    cout << qu.size(); // 1
    cout << qu.empty(); // 0
}

題目

ZJ e447 - Queue 練習

裸 Queue

UVa 10935 / ZJ e155

 - Throwing cards away I

現在手上有\(N\)張牌,由上而下編號\(1\)~\(N\)。當牌數量剩餘兩張以上時,丟掉最上面的一張,再把當前最上面的排放到底部。模擬輸出丟牌的過程?

UVa 540 / ZJ e564 -

Team Queue

(挑戰) 有一堆人要去排隊,每個人可能是\(M\)個組中其中一組的成員。當一個人要進去排隊時,若有同組的已經在隊伍裡,則可以插隊到自己組的尾端,否則就排到隊伍尾端。給\(M\)個組的組員和一堆enqueue / dequeue指令,模擬排隊情況。

Doubled-Ended Queue

雙向佇列

[de-queue] or [deck]

Deque

就是雙向的Queue拉 v.3

#include <deque>
int main()
{
    deque<int> dq; // 宣告
    
    dq.push_back(1); // 1
    dq.push_back(2); // 1 2
    dq.push_front(3); // 3 1 2
    dq.push_front(4); // 4 3 1 2
    cout << dq.front(); // 4
    cout << dq.back(); // 2
    
    // pop_front()
    // pop_back()
    // size()
    // empty()
}

Maybe No Judge

給一個序列 \(A\) 和一個數 \(K\),

問你這個序列每 \(K\) 個數的最大值和最小值為何?

\(1 \leq K \leq N \leq 10^6\)

\(A: [5, 1, 3, 2, 4], \ K=3\)

ZJ a146 - Sliding Window 但我RE,肝

我甚至不知道為什麼

TIOJ 1745 -

[APIO '10] Commando

給你 \(N\) 個士兵的戰力值 \(x_i\) ,要把他們分成一些連續的組別,一個組別的總戰力值計算方式為

\(x' = a(\sum{x_i})^2 + b\sum{x_i} + c\),

你希望所有組別的戰力和為最大,求總戰力和?

\(N \leq 10^6\)

我只是放爽的

中場! 常常用到的東西

  • vector

  • string

  • pair

vector

一些介紹

  • 哈哈其實是向量拉 (大誤
  • 動態的陣列
  • // 就是可以加大空間的
  • 跟一般陣列一樣可以直接存取
#include <vector>

int main()
{
    vector<int> v; // 宣告
    
    v.push_back(1); // 把1從後面加進去
    v.push_back(2); // 1 2
    v.push_back(3); // 1 2 3
    
    v.pop_back(); // 1 2
    
    cout << v[1]; // 2
                  // 跟陣列一樣
    
    cout << v.size(); // 2
                      // 有幾個元素
    
    cout << v.empty(); // 0
    
    cout << v.front() << ' ' << v.back() << '\n'; // 1 2
}

#include <vector>

int main()
{
    vector<int> v(10, 100);
    
    v.emplace_back(111);
    
    cout << v[9] << '\n'; // 100
    cout << v[10] << '\n'; // 111
    
    v.resize(5);
    v.clear();
}

進階用法

迭代器

v.begin() 、 v.end()

其實就是指標拉(X

v.begin() 是指向第一個元素的位置

v.end() 指向最後一個元素 "的下一個位置"

(之後可能會用到)

string

什麼這不是語法就講過了嗎:P

講師很偷餒

  • string 應該比你想的強大很多(?)
  • 其實就是 vector<char>
  • 但是因為太常用到了,所以特化出來的
  • 有很多功能待挖掘 :D
#include <string>

int main()
{
    string x = "aba", y = "abc";
    
    string z = x + y;
    cout << z << endl; // abaabc
    
    x += y;
    cout << x << endl; // abaabc
    
    cout << (x < y) << endl; // 1    
}

pair

  • 就是包了兩個變數的東西
  • 雖然有struct,但pair還是蠻常用的
  • 如果要包三個以上呢? :其實辦的到喔有時間再講
#include <utility>

int main()
{
    pair<int, int> a;
    pair<int, double> b;
    pair<string, int> c;
    
    a.first = 1, a.second = 2;
    b.first = 3, b.second = 4.321;
    c.first = "YAAAA", c.second = 6;
    
    pair<int, int> d = {7, 8};
}

稍微進階的資料結構

  • Heap 堆積

  • Set 集合

  • Map 映射

Heap 堆積

什麼是Heap?

每一個值都是一棵二元樹的節點,

對於一個節點,他的值都會比所有子節點大(or小)

根節點是最大值(or最小值)

:什麼是二元樹辣齁

性質

Heap是一棵平衡二元樹,深度最深與深度最淺相

差不超過1層。儲存二元樹可以用一個陣列來存,

如果是1-base,對於一個節點\(o\),

左子節點為\(2o\),右子節點為\(2o+1\)

支援的操作

  • 查詢最大值 - 查詢根節點

  • 插入一個值val

  • 刪除最大值 - 刪除根節點

累贅的實作

const int N = 1e5;
int heap[N], sz = 0;

int getMX(){
    return heap[1];
}

void push(int val){
    heap[++sz] = val;
    int idx = sz;
    while(heap[idx] > heap[idx/2] && idx/2 > 0)
        swap(heap[idx], heap[idx/2]), idx /= 2;
}

void pop(){
    swap(heap[1], heap[sz]), sz--;
    int idx = 1;
    while(heap[idx] > max(heap[idx*2], heap[idx*2+1]) && idx*2 <= sz){
        if(idx*2 == sz){ // 防止超出邊界
            if(heap[idx] > heap[idx*2])
                swap(heap[idx], heap[idx*2]);
            break;
        }
        if(heap[idx*2] > heap[idx*2+1])
            swap(heap[idx], heap[idx*2]), idx *= 2;
        else
            swap(heap[idx], heap[idx*2+1]), idx = idx*2+1;
    }
}
void pop(){
    swap(heap[1], heap[sz]), sz--;
    int idx = 1;
    while(heap[idx] > max(heap[idx*2], heap[idx*2+1]) && idx*2 <= sz){
        if(idx*2 == sz){
            if(heap[idx] > heap[idx*2])
                swap(heap[idx], heap[idx*2]);
            break;
        }
        if(heap[idx*2] > heap[idx*2+1])
            swap(heap[idx], heap[idx*2]), idx *= 2;
        else
            swap(heap[idx], heap[idx*2+1]), idx = idx*2+1;
    }
}

偷懶的東西 v.4

#include <queue>
int main()
{
    priority_queue<int> pq; // 宣告
    
    pq.push(2);
    pq.push(1);
    cout << pq.top(); // 2
    pq.push(3);
    cout << pq.top(); // 3
    pq.pop();
    cout << pq.top(); // 2
    
    // pq.size()
    // pq.empty()
}

Max Heap Property

by  priority_queue

偷懶的東西 v.4

#include <queue>
#include <vector>
int main()
{
    priority_queue<int, vector<int>, greater<int>> pq; // 宣告
    
    pq.push(2);
    pq.push(1);
    cout << pq.top(); // 1
    pq.push(3);
    cout << pq.top(); // 1
    pq.pop();
    cout << pq.top(); // 2
    
    // pq.size()
    // pq.empty()
}

Min Heap Property

priority_queue

其實不只最大最小值的Heap,

還可以自定義狀態,

之後碰到題目再說

題目

UVa 10954 / ZJ d221 -

Add All

(經典題) 給\(N\)個數字\(a_1, a_2, ..., a_N\),需要把數字兩兩相加直到剩下一個數字,但相加會有一個cost,cost計算方式為兩數之和,求合併所有數字的最小cost總和

TIOJ 1999 - 排隊買飲料

有一天,你經過了一家飲料店,發現有\(N\)個人排隊要買飲料。不過,這家飲料店人手充足,一共有\(M\)個店員可以服務這些排隊的人潮。
為了公平起見,雖然店員很多,但是排隊只排成一列,避免在排很多列的情況下,每列前進的速度會不一樣。另外,為了服務品質起見,這家店的店員必須遵守兩個規則:必須按照客人排隊順序服務客人,不能先服務排在後面的顧客,否則前面的客人可能會森77(而這是飲料店最不想看到的狀況);另外,如果某個店員服務了某個客人,則該客人點的所有飲料都要由該店員製作,製作完成之後才能服務下一位客人。

你做了一下市場調查,詢問每位排隊的客人要買幾杯飲料。假如所有的店員製作飲料的速度都是每分鐘1杯,在遵守這些規則的前提下,請問服務完這些客人至少需要多久?

\(N \leq 10^6, M \leq 10^4, a_n \leq 1000\)

Set 集合

概念

想像一下在這間教室裡會不斷進來一些人和出去一些人,你想要隨時知道某個人在不在這間教室裡?

或者就是數學上的集合辣

實作原理

簡單來說是維護一棵自動平衡二元樹

(通常是什麼紅黑樹、AVL Tree...),

每次二分搜看有沒有找到,實作非常複雜,

code通常都幾百行(?),有興趣自己去查>3

Treap or SplayTree or LinkCutTree

這就不偷懶ㄌ v.5

#include <set>
int main()
{
    set<int> st; // 宣告
    
    st.insert(3);
    cout << st.count(3); // 1
    cout << st.size(); // 1
    cout << st.count(5); // 0
    st.erase(5);
}

一般的Set同樣元素只能出現一個,

所以count只會是0或1

題目

TIOJ 1513 - 好多燈泡

你獲得了一張紙條,上面有\(N\)個數字,你要照著上面的數字開關燈泡,一開始所有燈泡都是關的,當到了第\(i\)個數字,需要操作第\(a_i\)個開關(開變關,關變開),已知最後只會剩下一個燈泡是開著的,求那個燈泡編號

\(N \leq 10^5,  a_i \leq 2^{31}\)

Hint : 其實有\(O(N)\)解,用位元運算

ZJ d708 - 小王的木 也是同樣的想法

ZJ a091 - 今晚打老虎

ZJ c421 / TIOJ 1911 - 雲端列印

維護一個集合,支援插入數字、

查詢最大最小值和刪除最大最小值

Hint : multiset

Map 映射

塞了pair的set而已!

有一個索引值 和 他的值

這就不偷懶ㄌ v.6

#include <map>
int main()
{
    map<int, int> mp; // 宣告
                      // {key, value}
    mp.insert(2);
    cout << mp[2]; // 1
    mp[2]++;
    cout << mp[2]; // 2
    cout << mp[1]; // 0
}

map的用法很多元,大家可以自己去查XD

題目

UVa 10226 / ZJ d492 -

Hardwood species

你獲得了一個工作!計算森林中各樹種的比例。有\(N\)棵樹,給樹種的英文名字,依照字典序輸出他們佔的比例

(比例計算方式:\( \frac{樹種出現的次數}{N} \))

耶沒了OAO

其實我不知道我做這份slides的時候為什麼要放鯊鯊可能壓力太大,記得多多關懷講師(X)

<bits/stdc++.h>

非毒瘤

但編譯會很慢,斟酌XD

ios_base::sync_with_stdio(false);

cin.tie(0);

優化輸入輸出

幾乎必加

但就不能使用 scanf、printf

如果還有時間...?

  • Bitset

  • Linked List

  • Tuple

基礎資料結構

By youou

基礎資料結構

  • 404