STL應用

Review

  • string
  • vector
  • queue、stack
  • (linked) list
  • deque

Review

  • priority_queue
  • set、map
  • unordered_map?

分類

  • Sequence Containers

  • (Ordered) Associative Containers

  • Unordered Associative Containers

  • Container Adaptors

注意事項

  • 每個容器都有empty()方法,表示容器是不是空的;如果試圖存取空容器的值可能會RE或取到亂數
  • 只有序列容器有索引值,當然是左閉右開,小心別讓i<0或i>=size()
  • 避免從空容器中讀取或pop東西
  • 對於序列容器來說,他們的第一項和最後一項分別是front()和back()
  • queue是front(), stack是top()

Iterator

  • 類似指標,指向一個變數,可以用*it來存取那個變數
  • set、map沒有辦法直接用i=[0,size())遍歷,怎麼辦?
  • 利用begin()、end()方法或for(type x:container)
  • find、lower_bound、upper_bound回傳的都是iterator
  • sort、fill等等也都需要傳入iterator
vector<int> vc;

for(vector<int>::iterator it = vc.begin(); it != vc.end(); ++it)
    cin >> *it;

for(auto It = vc.begin(); It != vc.end(); ++It)
    cout << *It << ' ';
cout << '\n';

int sum = 0;
for(int x:vc) sum += x;
cout << sum << '\n';

Iterator

分類 說明 例子
Input Iterator 可讀的迭代器
支援*it (但不能修改)
所有迭代器都是
Output Iterator 可寫的迭代器
支援*it (可以修改)
除了const_iterator以外都是
Forward iterator 支援it++和++it forward_list
Bidirectional iterator 支援++it, it++, --it, it-- list、大部分的關聯容器
Random Access
iterator 
支援it+k、it-k 原生指標及序列容器

Iterator

  • 正向迭代器 [begin(), end())
  • 反向迭代器 [rbegin(), rend())
  • 不常用的const_iterator: cbegin(), cend(), crbegin(), crend()
  • Adaptor通常沒有迭代器,不能直接遍歷
  • 建議熟悉range-based for loop,如果裡面的物件不容易複製或者想要修改裡面的值,請加上參照
vector<int> vi(100);
for(int &x:vi) cin >> x;
for(int x:vi) cout << x << '\n';

vector<map<int,int>> vim;
//input...
for(auto &mp:vim) cout << mp.rbegin()->first << '\n';

參照

可用來在函數中修改不同scope變數的值

#include <iostream>
using namespace std;
void swap(int &a,int &b) {
    if(a!=b) a^=b^=a^=b;
}
signed main() {
    int a,b,c;
    cin >> a >> b >> c;
    swap(a,b);
    swap(b,c);
    cout << a << ' ' << b << ' ' << c << '\n';
}

pair

//只是把兩個變數綁在一起的東西
#define ff first
#define ss second
typedef pair<int,int> pii;
pii p; // 放在全域會被預設初始化成{0,0}
pair<string,int> arr[100];
signed main() {
    for(int i = 0; i < 100; i++)
    	cin >> arr[i].ff, arr[i].ss = i;
    sort(arr, arr+100); // 會先比first,如果first一樣再比second
    // make_pair(a,b)會自動推斷型別,等價於 p = pair<int,int>(a,b)
    p = make_pair(arr[0].second, arr[1].second);
    cout << (p.ff*p.ff + p.ss*p.ss) << '\n';
    for(int i = 0; i < 100; i++) cout << arr[i].ss << ' ';
    cout << '\n';
}

bitset

const int N = 1000;
int n,m;
bitset<N> graph[N],vis; //空間很小(?)和bool[]比起來差了八倍左右
void dfs(int i) {
    vis[i] = true;
    for(int j = 1; j <= n; j++) if(graph[i][j] && !vis[j]) dfs(j);
}
signed main() {
    cin >> n >> m;
	for(int i = 0; i < m; i++) {
        cin >> a >> b;
        graph[a][b] = graph[b][a] = 1;
    }
    int CC = 0;
    for(int i = 1; i <= n; i++) if(!vis[i]) CC++, dfs(i);
    cout << CC << '\n';
}

大括弧(?)

可以用在部分函數的參數裡面

也可以拿來初始化STL容器或陣列

aggregate initialization

vector<int> fib{1,1,2,3,5,8};
map<char,int> mp{{'R',1}, {'P',2}, {'S',3}};
struct edge {int src, des, dis;};
int main() {
    int a,b,d,n,m;
    vector<edge> edges;
    cin >> n >> m;
    for(int i = 0; i < m; i++) {
        cin >> a >> b >> d;
        edges.push_back({a,b,d});
    }
    cout << max({7,1,2,2}) << '\n';
}

unordered系列

  • 基本上性質和map、set差不多
  • 不過內部是沒有排序過的,因此也沒有lower_bound、upper_bound
  • 似乎複雜度為\(O(N)\),但常數很大
  • 用非原生型別的時候還要自己寫hash function,有點麻煩
  • 壓常數(?) reserve、max_load_factor

https://codeforces.com/blog/entry/21853

unordered_map、unordered_set

multi系列

當你需要一個可以放重複元素的set?

multiset<int> ms;
ms.insert(7);
ms.insert(1);
ms.insert(2);
ms.insert(2);
auto LR = ms.equal_range(2); // a pair, [l,r)
auto it = ms.find(6); // if cannot find returns end
ms.erase(ms.find(2));

FUNCTIONS

  • min(a, b) / max(a, b)
  • min({a,b,c,...}) / max({a,b,c,...})
  • swap(a, b)
  • gcd(a, b) / __gcd(a, b) / lcm(a, b)
  • hypot(x, y) / sqrt(x) / pow(x, t)
  • floor(x) / ceil(x) / round(x)

FUNCTIONS

  • sort(first, last[, comp]);
  • stable_sort(first, last[, comp]);
  • lower_bound(first, last, value[, comp]);
  • upper_bound(first, last, value[, comp]);
  • binary_search(first, last, value[, comp]);
  • max_element(first, last[, comp]);
  • min_element(first, last[, comp]);
  • nth_element(first, nth, last[, comp]);

FUNCTIONS

set、map的版本請用member function
set<int> s;
s.insert(1);
s.insert(5);
s.insert(7122);
auto it = s.lower_bound(5); // return the node with key 5
int mi = *s.begin();
int mx = *prev(s.end());
// *s.rbegin()、*--s.end() also works, --operator doesn't really change the end
int have = s.count(7122);

FUNCTIONS

  • fill(first, last, value);
  • iota(first, last, value);
  • accumulate(first, last, init[, op]);
  • reverse(first, last);
  • unique(first, last[, eq]);
  • next_permutation(first, last[, comp]);
  • prev_permutation(first, last[, comp]);
  • random_shuffle(first, last[, gen]);
  • rotate(first, pos, last)
  • merge(first1, last1, first2, last2, out[, comp]);

自定義 compare

函數型

適用於函數裡面呼叫的參數和C的qsort(雖然寫法不太一樣)

bool cmp(int a, int b) {
    cout << "? " << a << ' ' << b << '\n';
    string verd;
    cin >> verd;
    return (verd == "<");
}
int id[100000];
signed main() {
    int n;
    cin >> n;
    iota(id, id+n, 1); // id = {1,2,...i}
    sort(id, id+n, cmp);
    for(int i = 0; i < n; i++) cout << id[i] << " \n"[i+1==n];
}

自定義 compare

lambda expression

適用在sort裡面,蠻簡潔的寫法

int n,val[100000];
signed main() {
    cin >> n;
    for(int i = 0; i < n; i++) cin >> v[i];
    sort(v,v+n,[](int a,int b){return a > b;});
    // sort(v,v+n,greater<int>());
}

自定義 compare

運算子&函數物件 FROM 學長的講義

相信大家都知道 int 可以做加減乘除、遞增遞減、模、位元運算、比較大小等等的運算;一個原生指標或迭代器可以 dereference,可以遞增,可能可以遞減,也可能可以做加減法運算;一個 vector 或 deque 則有下標運算 "[]"。這些我們統稱為「運算子」(operator)。

而在眾多的運算子中,還有一種 "()"。由於他的介面就像是函數一樣,因此提供 "()" 這種運算子的物件我們姑且稱作為「函數物件」,使用起來與函數幾乎沒有差別。至於函數物件的型別就稱作「函數型別」。

STL有less<T>和greater<T>等函數物件,可以把小於和大於的運算子包成函數物件供STL函式預設使用。

自定義 compare

函式類別

適用於priority_queue (雖然也可以用在普通的sort)

struct cmp {
    bool operator()(string a, string b) {
        return a.size() == b.size() ? a<b : a.size()<b.size();
    }
}
priority_queue<string,vector<string>,cmp> pq;
signed main() {
    int n,c;
    string x;
    cin >> n;
    for(int i = 0; i < n; i++) {
        cin >> c;
        if(c == 0) cin >> x, pq.push(x);
        else if(c == 1) cout << (pq.empty() ? pq.top() : -1) << '\n';
        else if(c == 2) {
            if(pq.empty()) cout << "error" << '\n';
            else pq.pop();
        }
    }
}

自定義 compare

自定義struct + operator<

幾乎全部通用

struct query {
    int l,r,id;
    bool operator<(const query &b) {
        if(l/400 != b.l/400) return l/400 < b.l/400;
        return r > b.r;
    }
} Q[100000];
int n,q,v[100000];
signed main() {
    cin >> n >> q;
    for(int i = 0; i < n; i++) cin >> v[i];
    for(int i = 0; i < q; i++) cin >> Q[i].l >> Q[i].r, Q[i].id = i;
    sort(Q,Q+q);
    // Mo's algo
}

自定義 compare

共通點

大部分的STL函數傳入的比較函式都是代表「小於」

也就是如果比較函數回傳1,第一個參數就會被放比較前面

 

同時因為三一律,我們通常只會定義小於,其他比較運算會由小於來定義

 

注意在STL的設計中,對於相同的元素的比較一定要回傳0 (因為 \(x < x\) 恆為假),否則可能會RE

STL應用

值域壓縮

離散化

假設值域足夠小,我們可以開一個BIT紀錄「有多少數字小於x」

然後從後面到前面依序查詢、插入

int BIT[1000025];
void add(int x) {
    for(; x <= 1000000; x += x&-x) BIT[x]++;
}
int qry(int x) {
    int r = 0;
    for(; x; x -= x&-x) r += BIT[x];
    return r;
}

可是值域有到\(2^{31}-1\) QQ

不是所有的數字都有必要開空間給他!

只有出現在原陣列裡面的數字才需要

並且答案只跟數字之間的大小關係有關連

\(\Rightarrow\) 把每個出現過的數字編號成「他是第幾大的」

vector<int> arr;
map<int> mp;
int cnt;
void compress() {
    mp.clear();
    for(int x:arr) mp[x] = 0;
    for(auto &p:mp) p.second = ++cnt;
    for(int &x:arr) x = mp[x];
}

開個map?

vector<int> arr, tmp;
void compress() {
    tmp = arr;
    sort(tmp.begin(), tmp.end());
    tmp.erase(unique(tmp.begin(), tmp.end()), tmp.end());
    for(int &x:arr)
        x = lower_bound(tmp.begin(), tmp.end(), x) - tmp.begin() + 1;
}

聽說這樣比較快

offline+sort v.s rb_tree

TIOJ

1045

1080

1667

1694

1840

超多逆序數對 三小

Output Tips

"string constant"[option]

UVa等OJ有時不允許行尾有多餘的空白,此時我們可以用下列方式簡潔的輸出(?)

int n, arr[100000];
int main() {
    cin >> n;
    for(int i = 0; i < n; i++) cin >> arr[i];
    for(int i = 1; i < n; i++) arr[i] += arr[i-1];
    for(int i = 0; i < n; i++) cout << arr[i] << " \n"[i==n-1];
}

"string constant"[option]

另一個例子

#include <iostream>
using namespace std;
const int K = 20;

signed main() {
    ios_base::sync_with_stdio(0), cin.tie(0);
    for(int x = -K; x <= K; x++) {
        for(int y = -K; y <= K; y++) {
            // r^2 <= k(r-x), (x^2+x^2+kx)^2 = k^2(x^2+y^2)
            cout << "#."[(x*x+y*y-x*5)*(x*x+y*y-x*5) <= 25*(x*x+y*y)];
        }
        cout << '\n';
    }
}

輸出固定精度

double ans = exp(2);
cout << fixed << setprecision(10) << ans << '\n';

fixed是代表不要用科學記號,和scientific相對

double ans = exp(2);
printf("%.10lf\n", ans);

I/O優化(?

ios_base::sync_with_stdio(0), cin.tie(0);
// use '\n' instead of endl

讀寫檔案

freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);

加了下面這兩行就可以直接從輸出到terminal改成輸出到檔案

可以用來生數字多一點的測資檢查

有些奇怪的比賽也會要求讀寫檔案

https://tioj.ck.tp.edu.tw/problems/1818

單調隊列

題敘: 給一個序列 \(<a_i>\),對每個 \(i\) 求最小的 \(j\) 使得 \(i<j\)且\(a_j \geq a_i\)

如果有兩個位置 \(x < y\) 使得 \(a_x \geq a_y\)

那麼 \(y\) 對所有在 \(x\)前面的數字都不會有影響

\(\Rightarrow\)由後往前做,要考慮的位置只有某個遞減的子序列

用一個stack儲存,維持stack內的單調性
將stack頂端與目前元素比較,不斷pop直到stack頂端大於目前元素
int n,h[1000025];
int ans[1000025];
void solve() {
    stack<int> stk; // 儲存index,以便計算答案
    for(int i = n-1; i >= 0; i--) {
        while(!stk.empty() && h[stk.top()] >= h[i]) stk.pop();
        ans[i] = (stk.empty() ? n-1 : stk.top());
        stk.push(i);
    }
}

TIOJ

1618

1566

1176

Made with Slides.com