1. 程式人生 > >資料結構番外篇【stl應用(1)】優先佇列

資料結構番外篇【stl應用(1)】優先佇列

stl是一種重要技巧,可以極大地簡化程式設計過程
在總結stl之前,我們先簡單介紹一下迭代器。
迭代器可以簡單理解為地址的等價物。
在不同資料型別中迭代器支援的操作略有不同
官方文件中的定義
其中vector使用的是隨機訪問迭代器,其支援的操作可以參考上述表格
雖然本文用不上預備知識,但是還是先說一下吧
接下來介紹常用stl——優先佇列

1.優先佇列

優先佇列(英文priority_queue)是一種維護集合最大最小值的資料結構(堆)
它能夠在O(1)時間得到一個集合的最大(小)值,在O(logn)時間加入新的元素或者刪除最大(小)的元素
常見操作如下:
優先佇列預設為大根堆,即維護最大值

#include <algorithm>
#include <vector>
#include <queue>
#include <iostream>
using namespace std;
priority_queue<int> q1;//預設大根堆
priority_queue<int,vector<int>,less<int> > q2;//等價的大根堆
priority_queue<int,vector<int>,greater<int> > q3;//小根堆
int main() { int n; scanf("%d",&n); for(int i = 1; i <= n; i ++) { int x;scanf("%d",&x); q1.push(x); q2.push(x); q3.push(x);//插入新元素 } while(!q1.empty())//檢查是否非空 { printf("%d\n",q1.top());//返回最大(小)值 q1.pop();//刪除最大(小)值 } while(q3.size()>0)//返回元素個數 { printf("%d\n",q3.top())
; q3.pop(); } }

優先佇列的優化十分優秀,很多情況下甚至比手寫heap還要快

例題

洛谷P3871 中位數
題目描述
給定一個由N個元素組成的整數序列,現在有兩種操作:
1 add a
在該序列的最後新增一個整數a,組成長度為N + 1的整數序列
2 mid
輸出當前序列的中位數
中位數是指將一個序列按照從小到大排序後處在中間位置的數。(若序列長度為偶數,則指處在中間位置的兩個數中較小的那個)
例1:1 2 13 14 15 16 中位數為13
例2:1 3 5 7 10 11 17 中位數為7
例3:1 1 1 2 3 中位數為1
輸入輸出格式
輸入格式:

第一行為初始序列長度N。第二行為N個整數,表示整數序列,數字之間用空格分隔。第三行為運算元M,即要進行M次操作。下面為M行,每行輸入格式如題意所述。
輸出格式:

對於每個mid操作輸出中位數的值
https://www.luogu.org/problemnew/show/P3871
這道題有很多做法,在此給出簡單且時間效率高的優先佇列做法
將陣列按照從小到大排序,將較小的一半放入一個大根堆,將較大的一半放入一個小根堆
每次新增數的時候與兩個堆堆頂元素比較,將其放入相應的堆中
確保左堆的元素個數>=右堆的元素個數>=左堆元素個數-1
若打破了這個條件,只需將某堆中的堆頂元素換到另一個堆即可
每一次詢問輸出左堆堆頂

#include <iostream>
#include <algorithm>
#include <set>
#include <cstdio>
#include <vector>
#include <queue>
using namespace std;
priority_queue <int,vector<int>,greater<int>> qright;
priority_queue <int,vector<int>,less<int>> qleft; 
vector <int> v;
int main() 
{
    int n,m;
    scanf("%d",&n);
    v.push_back(0);
    for(int i = 1; i <= n ;i++)
    {
        int x;
        scanf("%d",&x);
        v.push_back(x);
    }
    sort(v.begin()+1,v.end());
    for(int i = 1; i <= n/2; i ++)qleft.push(v[i]);
    for(int i = n/2+1; i <= n; i ++)qright.push(v[i]);
    scanf("%d",&m);
    while(m--)
    {
        char str[10];
        scanf("%s",str);
        if(str[0]=='m')
        {
            printf("%d\n",qleft.top());
        }
        else
        {
            int x;
            scanf("%d",&x);
            if(x<=qleft.top())
            {
                qleft.push(x);
            }
            else qright.push(x);
            int a = qleft.size(),b=qright.size();
            if(a-b>1)
            {
                qright.push(qleft.top());
                qleft.pop();
            }
            else if(b>a)
            {
                qleft.push(qright.top());
                qright.pop();
            }
        }
    }
}

https://www.luogu.org/problemnew/show/P1484
洛谷P1484 種樹
題目描述
cyrcyr今天在種樹,他在一條直線上挖了n個坑。這n個坑都可以種樹,但為了保證每一棵樹都有充足的養料,cyrcyr不會在相鄰的兩個坑中種樹。而且由於cyrcyr的樹種不夠,他至多會種k棵樹。假設cyrcyr有某種神能力,能預知自己在某個坑種樹的獲利會是多少(可能為負),請你幫助他計算出他的最大獲利。
輸入輸出格式
輸入格式:

第一行,兩個正整數n,k。
第二行,n個正整數,第i個數表示在直線上從左往右數第i個坑種樹的獲利。
輸出格式:

輸出1個數,表示cyrcyr種樹的最大獲利。
這道題採用的思想是在一棵樹被種之後,要改種左右兩顆樹獲利等於左右兩樹之和減去這棵樹,我們把這個獲利再當成之前被種的樹的獲利放入堆中,並且標記左右兩棵樹無效。
最後k次取堆頂元素求和

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <queue>
using namespace std;
#define N 500004
int f[N],l[N],r[N],n,k,cnt,tot;
long long ans;
bool vis[N<<2];
struct num
{
 int pos,val;
 bool operator < (const num b)const
 {
  return val<b.val;
 } 
}buf; 
priority_queue <num> heap;
int main()
{
 scanf("%d%d",&n,&k);
 for(int i = 1; i <= n; i ++)
 {
  scanf("%d",f+i);
  heap.push((num){i,f[i]});
  vis[i]=1;
  r[i]=i+1;
  l[i]=i-1;
 }
 for(int i = 1; i <= k ; i ++)
 {
  while(!heap.empty()&&!vis[heap.top().pos])heap.pop();
  if(heap.empty())break;
  buf=heap.top();
  heap.pop();
  vis[r[buf.pos]]=vis[l[buf.pos]]=0;
  if(buf.val<0)break;
  ans+=buf.val;
  heap.push((num){buf.pos,f[buf.pos]=f[r[buf.pos]]+f[l[buf.pos]]-buf.val});
  r[buf.pos]=r[r[buf.pos]];l[r[buf.pos]]=buf.pos;
  l[buf.pos]=l[l[buf.pos]];r[l[buf.pos]]=buf.pos;
  vis[buf.pos]=1;
 }
 cout<<ans;
}