{algo[3]}

By 想回家看直播的 佑佑

啊他們怎麼都用algo當標題

他們是不是在排擠我

好嘛那我也用algo

今天要教什麼

  • priority queue
  • list
  • set
  • map
  • sort
  • algorithm
  • 題單

今天這裡放的是

被嚇到的晴

Priority queue

優先佇列

複習一下queue

  • 佇列

  • 排隊

  • 先進先出

  • 先來先走

  • FIFO

priority 

priority_queue

有優先級的佇列

aka 會幫你排序好

宣告

記得要有<queue>函式庫

宣告

宣告方式

priority_queue<型別,容器,排列方式> 名字;
priority_queue<int> pq;
priority_queue<int,vector<int> > pq;
priority_queue<int,vector<int>,less<int> > pq;
//這三個一模一樣喔
//容器預設vector,排列方式預設less(由大排到小)

型別:int,long long,pair<int,int>之類的

容器:反正就用vector就對了

排列方式:less<型別> 由大排到小

                  greater<型別>由小排到大

宣告

可以#define pq priority_queue;

那你就可以

pq<int> mypq;

.push()

把東東推進pq中並排列

priority_queue<int> pq;
pq.push(3);//{3}
pq.push(7);//{7,3}
pq.push(5);//{7,5,3}

O(log N)

.top()

回傳pq裡最前面的值

priority_queue<int> pq;
pq.push(3);//{3}
pq.push(7);//{7,3}
pq.push(5);//{7,5,3}
cout << pq.top();//輸出7

O(1)

.pop()

把pq裡最前面的值踢掉

priority_queue<int> pq;
pq.push(3);//{3}
pq.push(7);//{7,3}
pq.push(5);//{7,5,3}
cout << pq.top();//輸出7
pq.pop();//{5,3}
cout << pq.top();//輸出5

O(log N)

.size()

回傳pq的大小

priority_queue<int> pq;
pq.push(3);//{3}
pq.push(7);//{7,3}
pq.push(5);//{7,5,3}
cout << pq.top();//輸出7
pq.pop();//{5,3}
cout << pq.top();//輸出5
cout << pq.size();//輸出2

O(1)

.empty()

回傳pq是否為空

priority_queue<int> pq;
pq.push(3);//{3}
pq.push(7);//{7,3}
pq.push(5);//{7,5,3}
cout << pq.top();//輸出7
pq.pop();//{5,3}
cout << pq.top();//輸出5
cout << pq.size();//輸出2
cout << pq.empty();//輸出1

由於是回傳「是否」為空

所以回傳值為一個bool值

當pq裡有東西都回傳1

沒東西時回傳0

O(1)

自訂排序函式

#include<bits/stdc++.h>
using namespace std;
struct comp{
    bool operator()(int a,int b){
        return a<b;
    }
};
int main(){
    priority_queue<int,vector<int>,comp> pq;
}

這是由小到大

By 807的神奇寫法

當你不想用struct寫一個排列函式

#include<bits/stdc++.h>
using namespace std;
int main(){
    priority_queue<int,vector<int>,decltype([](int a,int b){return a<b;})> pq;
    pq.push(5);
    pq.push(7);
    cout<<pq.top()<<endl;
}

一樣是由小到大

啊807說他也不知道decltype是什麼

溫馨提醒

  • 你們可能會發現pq的函式做不到對中間項改值

啊所以不要像我一樣寫出pq[3]之類的東西

  • pq是push()
  • vector是push_back()

啊所以一樣不要像我一樣寫出pq.push_back()

linked_list

鍊接串列

趁著你們還清醒講點難搞的

那我們先來看例題 by失蹤

賽車比賽,給你n個人,初始是編號1在第一名,編號2在第二名,編號n在第n名

也可能有人直接被踢出遊戲

會想怎麼做?

會告訴你哪個編號超越了前一個人

$$1\,\le\,n\,\le\,10^5$$

那我們先來看例題 by失蹤

第一反應就開個array,有人被超越就swap他們兩個

有人被踢出遊戲就把他的值改成0之類的

但是會太慢導致你吃TLE

這樣完全可以做

Why?

那我們先來看例題 by失蹤

因為有人會被踢出遊戲

所以每次要swap的時候都要判斷下一項是不是0

所以

就太久了

最多有100000個人

linked_list是什麼

  • 每個點存自己的值
  • 每個點會知道前一個點和下一個點
  • 可以往兩個點之間插入點

linked_list是什麼

失蹤:

既然要存每個點的值

那我們就開一個陣列啊

既然要存每個點往後接到哪個點

那就再開一個陣列啊

既然要存每個點往前接到哪個點

那就再開一個陣列啊

宣告

pre代表每一個點的前一個點是val中的第幾項

val用來存值

nxt代表每一個點的下一個點是val中的第幾項

先用表格來示範一下

宣告

pre
val
nxt

改值

我希望點與點連結起來的樣子

1

pre
val 1
nxt

改值

我希望點與點連結起來的樣子

1 -> 2

pre 0
val 1 2
nxt 1

2的前一項是1

1在val陣列中是第0項

1的下一項是2

2在val陣列中是第1項

我希望點與點連結起來的樣子

1 -> 2 -> 3 -> 4

pre 0 1 2
val 1 2 3 4
nxt 1 2 3

改值

我希望點與點連結起來的樣子

1 -> 3 -> 2 -> 4

pre 0 1 2
val 1 2 3 4
nxt 1 2 3

2的下一項是4

4在val陣列中是第3項

改值

我希望點與點連結起來的樣子

1 -> 3 -> 2 -> 4

pre 0 1 2
val 1 2 3 4
nxt 1 3 3

3的下一項是2

2在val陣列中是第1項

4的前一項是2

2在val陣列中是第1項

改值

我希望點與點連結起來的樣子

1 -> 3 -> 2 -> 4

pre 0 1 1
val 1 2 3 4
nxt 1 3 1

3的下一項是2

2在val陣列中是第1項

4的前一項是2

2在val陣列中是第1項

改值

我希望點與點連結起來的樣子

1 -> 3 -> 2 -> 4

pre 2 0 1
val 1 2 3 4
nxt 2 3 1

以此類推

改值

宣告

int pre[200000];
int val[200000];
int nxt[200000];

插入

cin >> val[x];
pre[x] = a;//把x的前一項改成a
nxt[x] = b;//把x的後一項改成b
pre[b] = x;//把b的前一項改成x
nxt[a] = x;//把a的後一項改成x

把x插到a,b之間

交換

nxt[pre[x]] = nxt[x];//把x的前一項的下一項改成x的下一項
pre[nxt[x]] = pre[x];//把x的下一項的前一項改成x的前一項
nxt[x] = pre[x];//把x的下一項改成x的前一項
pre[x] = pre[pre[x]];//把x的前一項改成x的前一項的前一項
nxt[pre[pre[x]]] = x;//把x的前一項的前一項的下一項改成x
pre[nxt[nxt[x]]] = x;//把x的下一項的下一項的前一項改成x

x會超過x前面的車

我是小丑

int runner_pr=prv[x];
int hum=nxt[x];
nxt[prv[prv[x]]]=x;
prv[nxt[d]]=runner_pr;
prv[x]=prv[prv[x]];
nxt[x]=runner_pr;
prv[runner_pr]=x;
nxt[runner_pr]=hum;

刪除

nxt[pre[x]] = nxt[x];//把x前一項的下一項改成x的下一項
pre[nxt[x]] = pre[x];//把x下一項的前一項改成x的前一項
nxt[x] = -1;//x的下一項改成-1
pre[x] = -1;//x的前一項改成-1

把x從陣列中刪除

-1代表前/後沒有相連的東西

大概就是這樣,三個陣列的做法你會看到一堆陣列名字和[]

如果你實在看不懂上面的東西

可以看看去年的struct+指標版

雖然linked_list我幾乎沒用過

set

集合

set是什麼

  • 具有唯一性
  • 內容經過排序
  • 好多衍生東東

當然要用之前要 #include<set>

順帶一提它是一棵紅黑樹

宣告

set<型態> 命名

#define pii pair<int,int>

set<int> num;
set<pii> score;

預設從小排到大

.insert()

把值插入set中

如果set中已有相同元素則無事發生

set<int> num;
num.insert(3);//{3}
num.insert(5);//{3,5}
num.insert(3);//{3,5}

O(log N)

.count()

回傳set中某個元素的數量

因為在set中數量非0即1,所以可以拿來當bool用

set<int> num;
num.insert(3);//{3}
num.insert(5);//{3,5}
num.insert(3);//{3,5}
num.count(1);//回傳0
num.count(3);//回傳1

O(log N)

.erase()

踢除set中某個元素,並回傳true

若沒有該元素可踢除,則回傳false

set<int> num;
num.insert(3);//{3}
num.insert(5);//{3,5}
num.insert(3);//{3,5}
while(num.erase(3)){
    cout << "byebye";
}

O(log N)

.size()

回傳set剩餘的元素數量

set<int> num;
num.insert(3);//{3}
num.insert(5);//{3,5}
num.insert(3);//{3,5}
cout << num.size();//輸出2

O(1)

.empty()

回傳一個bool,代表set是否為空

set<int> num;
num.insert(3);//{3}
num.insert(5);//{3,5}
num.insert(3);//{3,5}
if(!num.empty()){
    cout << "something in the set\n"
    num.erase(3);
    num.erase(5);
}
else{
    cout << "now nothing in the set\n"
}
//輸出
//something in the set
//now nothing in the set

O(1)

.clear()

強制清空set中所有元素

set<int> num;
num.insert(3);//{3}
num.insert(5);//{3,5}
num.insert(3);//{3,5}
if(!num.empty()){
    num.clear();
}
//{}

O(N)

unordered_set

顧名思義,未經排序的set

語法與set基本一致

只是將宣告的set改成unordered_set

set unordered_set
.insert() O(log N) O(1)
.erase() O(log N) O(1)
.count() O(log N) O(N)

unordered_set

unordered_set<int> num;
num.insert(10);{10}
num.insert(2);{10,2}
num.clear();
if(num.empty()){
    cout << "nothing";
}
else{
    cout << "something";
}
//輸出nothing

multiset

顧名思義,有重複元素的set

語法與set基本一致

只是將宣告的set改成multiset

有重複元素

所以.count()會回傳該元素的數量喔

multiset

multiset<int> num;
num.insert(1);//{1}
num.insert(1);//{1,1}
num.count(1);// 2
num.clear();
if(num.empty()){
    cout << "nothing";
}
else{
    cout << "something";
}
//輸出nothing

map

關聯式容器

map是什麼

  • 它看起來好陣列喔
  • 沒那麼多衍生東東

當然要用之前要 #include<map>

順帶一提它也是一棵紅黑樹

map是什麼

  • 自訂index(key)
  • key不侷限於數字
  • key有唯一性
  • 一個key對一個value
  • 也會自動排序

啊它就是

index可以不為數字的

array啊

宣告

map<key型態,value型態> 命名

map <int, int> num; // int 對到 int
map <char, int> a; // char 對到 int
map <string, int> animal; // string 對到 int

加入一個新的key

map <string, string> couple;
couple.insert({"晴", "佑佑"});

你可以

但是insert是壞東西

更簡單的做法是跟array一樣

map <string, string> couple;
couple["晴"] = "佑佑";

O(log N)

取值

啊就,取值,你在 array 中怎麼做,在 map 中就怎麼做

map <string, string> museum;
museum["晴"] = "顯然我是清純小女僕";
museum["世宗"] = "我超勝利";
museum["佑佑"] = "沒關係我先逝世";
museum["Cc"] = "HHPY";
museum["水獺"] = "硬起來了";
museum["柴柴"] = "這就是標準的斷章取義";
museum["807"] = "你檢查腋下";
cout << museum["佑佑"];
//沒關係我先逝世

O(log N)

.clear()

啊就,清空

map <int, int> dic;
dic[71] = 1e9 + 17;
cout << dic[71] << '\n'; //1000000017
dic.clear();
cout << dic[71] << '\n'; //沒看過的 key 預設是 0

O(N)

小問題

key一樣從0開始往後加,並且可以隨時新增value

學長你有什麼問題幹嘛不用vector就好

小問題

學長你有什麼問題幹嘛不用vector就好

事實上有時候map會優於vector

小問題

假如今天題目給n

$$1\,\le\,n\,\le\,1e15$$

用vector開1e15的大小?

放心時間空間都爛給你看

小問題

假如今天題目給n

$$1\,\le\,n\,\le\,1e15$$

但你發現這麼大的範圍總共只會給你10筆資料

這時候map只要開10格就解決了

所以你會發現

當數據離散時

map是好東西

unordered_map

顧名思義,未經排序的map

語法與map基本一致

只是將宣告的map改成unordered_map

好眼熟的前綴

map unordered_map
加key O(log N) O(1)
改值 O(log N) O(1)

algorithm

演算法函式庫

好耶最簡單的部分

<algorithm>

標頭檔之一,用之前記得

#include <algorithm>

幫你寫好一堆好用的函式

其實萬用標頭檔就包含這個了

不用什麼都手刻了好感動

一些常用的好東西

max(a,b)

min(a,b)

回傳a,b中較大的那一個

回傳a,b中較小的那一個

int a=0;
int b=1;
cout << max(a,b);
//輸出1
cout << min(a,b);
//輸出0

一些常用的好東西

max(a,b)

min(a,b)

順帶一提,如果你要比較三個以上的數據的話

int a=0;
int b=1;
int c=2;
cout << max(a, max(b,c));
//輸出2

請在max/min裡面包max/min

一些常用的好東西

swap(a,b)

將a,b中的數值交換

a,b可以是任何STL或變數

但必須同型態

int a=7;
int b=17;
swap(a,b);
cout << a << " " << b;
//輸出17 7

插播一下

你們還記得iterator(迭代器)是什麼嗎

忘了去翻上節課簡報

這邊只舉一些例子

.begin()

表示首位元素的記憶空間位址

.end()

表示末位元素的下一個的記憶空間位址

一些常用的好東西

lower_bound(iterator,iterator,value)

upper_bound(iterator,iterator,value)

lower_bound回傳的指標指向

「大於等於」value的「最小值」

回傳一個指標

upper_bound回傳的指標指向

「大於」value的「最小值」

聽起來是很繞口啦

對排序好的資料

一些常用的好東西

lower_bound(iterator,iterator,value)

upper_bound(iterator,iterator,value)

vector<int> = {1,2,3,4,5};
auto lower = lower_bound(arr.begin(),arr.end(),3);
auto upper = upper_bound(arr.begin(),arr.end(),3);
cout << &lower << " " << &upper;
//輸出3 4

順帶一提,auto是用來自動判別資料型態然後宣告的

像他在這裡就會偵測到lower_bound回傳的是指標

於是自動把lower宣告成一個指標

這樣就不用一直加*了好耶

一些常用的好東西

lower_bound(iterator,iterator,value)

upper_bound(iterator,iterator,value)

vector<int> = {1,2,3,4,5};
auto lower = lower_bound(arr.begin(),arr.end(),3);
auto upper = upper_bound(arr.begin(),arr.end(),3);
cout << &lower << " " << &upper;
//輸出3 4

arr中

大於等於3的最小值為3
故lower = 3

大於3的最小值為4
故upper = 4

先忽略這個白到哭的顏色

這是去年你們學長的學長遇到的困擾

這是去年你們學長的學長的學長的簡報

這是今年你們學長的簡報

明年的學術,你知道該怎麼做了吧

學長的意思是什麼?

當你今天要排序10000個數字

學長be like:寫個for迴圈,每次swap兩個數字

學長哪裡沒寫好:需要兩個for迴圈

for(int i = 0; i < 10000; i++){
    for(int j = 0; j < 10000-i; j++){
        if(arr[j] >= arr[j+1]){
            swap(arr[j], arr[j+1]);
        }
    }
}

每次run完整個陣列一定會把最大/最小值搬到最右邊

但不一定把最小/最大值搬到最左邊

$$O(N^2)$$

怎麼做快一點?

有沒有不用每次要排序的時候都要磨掉一堆指紋的寫法

有!

std::sort

它甚至是O(N log N),超級快

sort

int arr[10]{2,4,1,5,3,8,6,9,7,10};
sort(arr,arr+10);//預設從小排到大
//{1,2,3,4,5,6,7,8,9,10}
sort(arr,arr+10,greater<int>);
//{10,9,8,7,6,5,4,3,2,1}

sort(iterator,iterator,排列方式)

排列方式有

less<int>
greater<int>

啊你如果要其他的就像前面一樣自己寫一個函式囉

提供一下愛用array的人的寫法

sort

vector<int> vec = {2,4,1,5,3,8,6,9,7,10};
sort(vec.begin(),vec.end());//預設從小排到大
//{1,2,3,4,5,6,7,8,9,10}
sort(vec,begin(),vec.end(),greater<int>);
//{10,9,8,7,6,5,4,3,2,1}

sort(iterator,iterator,排列方式)

排列方式有

less<int>
greater<int>

啊你如果要其他的就像前面一樣自己寫一個函式囉

顯然vector特別好用

有比sort更快的方法嗎

肯定有啊

Bogo Sort 猴子排序

最優的情況是O(1)

最壞的情況是O(∞)

學長你做簡報做瘋囉

O(∞)不就永遠做不完

有比sort更快的方法嗎

Bogo Sort 猴子排序

最優的情況是O(1)

最壞的情況是O(∞)

你運氣夠好就隨機一次剛好排成順序

運氣不好就排到死都排不好

對整個要排序的容器做random

然後檢查排好了沒

題單

TIOJ

1682

NEOJ

2024

1111

0059

0020

0021

ISCOJ

4476

4477

Made with Slides.com