0%

0-22 priority_queue

堆(heap)

在 set 的章節中我們學過,set 可以隨時維護一個排序好的集合。但這相當難以實作,並且雖然複雜度是 $O(logN)$,但有著較大的常數。

如果,我不需要那麼多功能,只需要支援:加入一個元素、查詢最大元素、刪除最大元素這幾個操作,其實有個比較快的方法:heap

heap 介紹

查詢最大值

直接回傳 1 號節點數字。

刪除最大值

將 N 號移到 1 號位置,接著不停與其兒子比較,將其與較大的兒子交換,直到其無法再交換為止。

插入值

將新的值加入 N+1 號位置,不停與其父親比較,直到其值小於父親為止。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
int heap[1000005], N = 0;

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

void del(){
assert(N > 0);
heap[1] = heap[N];
N--;
for(int i=1,to;i*2<=N;i=to){
if(i*2+1 > N || heap[i*2] > heap[i*2+1]) to = i*2;
else to = i*2+1;
if(heap[i] > heap[to]) break;
swap(heap[i], heap[to]);
}
}

void ins(int x){
N++;
heap[N] = x;
for(int i=N;i>1;i>>=1){
if(heap[i/2] > heap[i]) break;
swap(heap[i/2], heap[i]);
}
}

而在 STL 中,有個名為 priority_queue 的結構,其內部便是以 heap 實作。它能夠做到 $O(1)$ 查詢最大值、$O(logN)$ 加入元素、$O(logN)$ 刪除最大值。

由於功能完全是 set 的子集,set 能任意刪而 priority_queue 只能刪除最大值,因此大多數時候使用 set,只有在 set 被卡時間以及特定演算法上會使用到 priority_queue。

priority_queue

宣告

1
priority_queue<int> pq;

加入元素

1
pq.push(1);

查詢最大

1
cout << pq.top() << '\n';

刪除最大

1
pq.pop();

更改比較函式

與 set 不同的是,priority_queue 其實有三個參數,分別為型態、底層容器、以及比較函式。若不需更改比較函式,宣告時只需第一參數即可,但若需要自訂比較函式,則需三個都寫出來:

1
priority_queue<int, vector<int>, greater<int>> pq;

另一個比較反直覺的東西,則是排在 priority_queue 頂的元素,其實是跟比較函式相反的。例如說,預設的 less<> 函式,排在最上方的反而是最大的元素。若要得到最小的元素,則須使用 greater<> 比較函式。

例題

把之前 set 題裡的拿來用

TIOJ 1161

★★★★TIOJ 1231

AC Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 枚舉其中一個點數
#include<bits/stdc++.h>
using namespace std;
#define pii pair<int,int>

priority_queue<int> pq;
int n, k;
pii arr[1000005];

void solve(){
while(!pq.empty()) pq.pop();
cin >> n >> k;
for(int i=1;i<=n;i++) cin >> arr[i].first >> arr[i].second;
sort(arr+1, arr+n+1);
int ans = 1e9;
for(int i=1;i<=n;i++){
pq.push(arr[i].second);
if(pq.size() > k) pq.pop();
if(pq.size() == k) ans = min(ans, arr[i].first + pq.top());
}
cout << ans << '\n';
}

int main(){
int t; cin >> t; while(t--) solve();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
//TIOJ 1231
//從後面考慮回來
//最後一分鐘該放什麼呢?
//那倒數第二分鐘呢?
#include<bits/stdc++.h>
#define ll long long
#define pii pair<ll,ll>
using namespace std;

priority_queue<ll> pq;

pii arr[100005];

int n, k;

int main(){
ios::sync_with_stdio(0), cin.tie(0);
cin >> n;
for(int i=1;i<=n;i++) cin >> arr[i].second >> arr[i].first;
sort(arr+1, arr+1+n, greater<pii>());

cin >> k;
ll t = k, idx = 1, ans = 0;
priority_queue<int> pq;

while(t > 0){
while(idx <= n && arr[idx].first >= t){
pq.push(arr[idx].second);
idx++;
}
if(pq.empty()){
ans -= t - arr[idx].first;
t = arr[idx].first;
}
else{
ans += pq.top();
pq.pop();
t--;
}
}
cout << ans << '\n';
}