pb_ds(Policy-Based Data Structures)

来源:互联网 发布:gre模拟考试软件 编辑:程序博客网 时间:2024/05/11 03:23

参考:http://blog.csdn.net/wjf_wzzc/article/details/38851703

《C++的pb_ds库在OI中的应用》大连市第二十四中学 于纪平

http://blog.csdn.net/wmdcstdio/article/details/44596305

一、

pb_ds 库全称 Policy-Based Data Structures

It is designed for high-performance, flexibility, semantic safety, and conformance to the corresponding containers in std and std::tr1.

英文官方文档:https://gcc.gnu.org/onlinedocs/libstdc++/ext/pb_ds/

pb_ds 库封装了很多数据结构,比如哈希(Hash)表,平衡二叉树,字典树(Trie树),堆(优先队列)等

就像 vector、set、map 一样,但比STL 功能更多

pb_ds 只在 Linux 下可以用


二、基本用法

1、优先队列(Priority Queue)


#include<ext/pb_ds/priority_queue.hpp> using namespace __gnu_pbds;

与 std::priority queue 的基本用法相同,包括 size(),empty(),push(const T),top(),pop(),clear()

模版参数:

template

<

typename Value_Type ,

typename Cmp_Fn = std :: less < Value_Type >,

typename Tag = pairing_heap_tag ,

typename Allocator = std :: allocator <char >

>

class priority_queue

Tag 表示所使用的堆的类型

可以是配对堆(pairing_heap_tag)、二叉堆(binary_heap_tag)、二项堆(binomial_heap_tag)、经改良的斐波那契堆(thin_heap_tag)、

冗余计数二项式堆(rc_binomial_heap_tag)等

可以用 begin() 和 end() 来获取 iterator 从而遍历

可以 increase_key,decrease_key 和删除单个元素

可以合并

* point iterator push(const reference)

* void modify(point iterator, const reference)

* void erase(point iterator)

priority_queue <int > p;

priority_queue <int >:: point_iterator it = p. push (0);

p. push (1); p. push (2);

p. modify (it , 3);

assert (p. top () == 3);

p. erase (it );

assert (p. top () == 2);

* void join(priority queue &other)

把 other 合并到 *this,然后 other 会被清空

共有 5 种操作:push、pop、modify、erase、join

配对堆(pairing_heap_tag):push 和 join 为 O(1),其余为均摊 O(logn)

二叉堆(binary_heap_tag) :只支持 push 和 pop,均为均摊 O(logn)

二项堆(binomial_heap_tag):push 为均摊 O(1),其余为 O(logn)

冗余计数二项式堆(rc_binomial_heap_tag):push 为 O(1),其余为 O(logn)

经改良的斐波那契堆(thin_heap_tag):push 为 O(1),不支持 join,其余为 O(logn);但是如果只有 increase_key,那么 modify 为均摊 O(1)

“不支持”不是不能用,而是用起来很慢

通常需要合并的时候就用配对堆(pairing_heap_tag)

做 Dijkstra 的时候就用经改良的斐波那契堆(thin_heap_tag)

真的是这样吗?

实践是检验真理的唯一标准!

性能测试:

参与测试的有:

std::priority_queue,配对堆(pairing_heap_tag)、二叉堆(binary_heap_tag)、二项堆(binomial_heap_tag)、经改良的斐波那契堆(thin_heap_tag)、冗余计数二项式堆(rc_binomial_heap_tag),手写线段树

评测机:

Windows,i5-3210M 2.50GHz,gcc version 4.4.0 (GCC)

测试1:堆排序

测试2:堆的合并

测试3:堆优化 Dijkstra 算法

不开 O2

开 O2

大致结论

2、平衡二叉树(Balanced Binary Tree)

在刷题过程中我们经常使用到set和map,也知道它们的内部实现是红黑树,但我们显然无法按照操作平衡二叉树的方式操作它们,甚至连平衡二叉树最基本的求kth和求rank操作都无法在O(logn)内完成

好在pb_ds库给了一个并不是很强大,但在一些时候足够用的解决方案

pb_ds库内置了红黑树(Red Black Tree)、伸展树(Splay Tree)等

这些封装好的树都支持插入(insert)、删除(erase)、求区间第 k 大(find_by_order)、求 rank(order_of_key)操作

声明变量时的参数(模板参数):

第一个是键(key)的类型

第二个是值(value)的类型,null_type表示没有值,简单地理解就是表明这是 set 而不是 map,有些稍旧的版本需要写成 null_mapped_type才可以

第三个表示比较函数,默认为 less<Type>

第四个为平衡二叉树的类型,默认为红黑树rb_tree_tag

第五个代表元素的维护策略,只有当使用 tree_order_statistics_node_update 时才可以求 kth 和 rank,此外还有 null_tree_node_update(默认值)等

注意:

求 kth(find_by_order)返回的是迭代器,求 rank 返回的是值,两者都是从 0 开始计算的

BZOJ 3224 Tyvj 1728 普通平衡树

解题思路:

需要实现一个支持点插入,点删除,查询rank,求第k大,求前驱/后继的数据结构正好可以用pb_ds中的tree实现pb_d s中 tree 的功能:

set / map 支持的所有功能

还包括查询 rank,求第 k 大,按值分割(把值 >=x 的割到另一棵树中)

合并值域不相交的两棵树为了实现查询 rank 和求第 k 大,必须加上 tree_order_statistics_node_update

注意:

 ①null_mapped_type 是用来占位的,如果它是一个真正的数据类型那就是 map,如果像这样就是 set

在一些编译器上(如 COGS 、BZOJ 和SPOJ 的编译器)必须用 null_mapped_type,在另一些编译器上必须用 null_type②即使键类型是 long long,你仍然可以强行把比较器定义成less或者greater,当然会出bug③pb_ds 不资瓷类似 multiset / multimap 的玩意,你必须自行搞一个偏移量

可以把每个元素乘以2^20后加上偏移量,偏移量是 "当前重复了几次",再用一个 map 来维护 “每个元素出现了几次”,前驱后继直接在 map 上计算

#include <iostream>#include <cstdio>#include <cstring>#include <cstdlib>#include <algorithm>#include <queue>#include <vector>#include <stack>#include <map>#include <cmath>#include <cctype>#include <ext/pb_ds/assoc_container.hpp>using namespace std;using namespace __gnu_pbds;typedef long long ll;typedef unsigned long long ull;typedef unsigned int uint;typedef tree<ll, null_mapped_type, less<ll>, rb_tree_tag, tree_order_statistics_node_update> SuperTree;const ull mod = 1e9 + 7;const int INF = 0x7fffffff;SuperTree Tree;map<int, int> Map;int main(){#ifdef __AiR_H    freopen("in.txt", "r", stdin);#endif // __AiR_H    int n;    scanf("%d", &n);    while (n--) {        int opt;        ll x;        scanf("%d%lld", &opt, &x);        if (opt == 1) {            Tree.insert((x<<20) + Map[x]++);        } else if (opt == 2) {            Tree.erase(Tree.find((x<<20) + (--Map[x])));            if (Map[x] == 0) {                Map.erase(Map.find(x));            }        } else if (opt == 3) {            printf("%d\n", Tree.order_of_key(x<<20) + 1);        } else if (opt == 4) {            printf("%lld\n", *Tree.find_by_order(x-1)>>20);        } else if (opt == 5) {            map<int, int>::iterator itr = Map.lower_bound(x);            --itr;            printf("%d\n", itr->first);        } else {            map<int, int>::iterator itr = Map.upper_bound(x);            printf("%d\n", itr->first);        }    }    return 0;}

UESTC 1339 郭大侠与线上游戏

可以查询区间第 k 大,然后 k 取中位数

#include <iostream>#include <cstdio>#include <cstring>#include <cstdlib>#include <algorithm>#include <queue>#include <vector>#include <stack>#include <map>#include <cmath>#include <cctype>#include <ext/pb_ds/assoc_container.hpp>using namespace std;using namespace __gnu_pbds;typedef tree<int, null_type, less<int>, rb_tree_tag, tree_order_statistics_node_update> set_t;typedef long long ll;typedef unsigned long long ull;typedef unsigned int uint;const ull mod = 1e9 + 7;const int INF = 0x7fffffff;const int maxn = 1e6 + 10;queue<int> Q;set_t s;int main(){#ifdef __AiR_H    freopen("in.txt", "r", stdin);//    freopen("out.txt", "w", stdout);#endif // __AiR_H    int n;    int cmd, x;    scanf("%d", &n);    while (n--) {        scanf("%d", &cmd);        if (cmd == 1) {            scanf("%d", &x);            s.insert(x);            Q.push(x);        } else if (cmd == 2) {            s.erase(Q.front());            Q.pop();        } else {            int k = Q.size() / 2;            printf("%d\n", *s.find_by_order(k));        }    }    return 0;}

BZOJ 1503 [NOI2004]郁闷的出纳员
解题思路:

要求实现:插入,求第k大,删除小于某个值的元素(需要记录删了多少)
“删除小于某个值的元素”用 split (按值分割)实现,用 greater 比较,删除的命令类似于 Tree.split(low,Tree_t),其中 Tree_t 是一棵无用的树

#include <iostream>#include <cstdio>#include <cstring>#include <cstdlib>#include <algorithm>#include <queue>#include <vector>#include <stack>#include <map>#include <cmath>#include <cctype>#include <ext/pb_ds/assoc_container.hpp>using namespace std;using namespace __gnu_pbds;typedef long long ll;typedef unsigned long long ull;typedef unsigned int uint;typedef tree<ll, null_mapped_type, greater<ll>, rb_tree_tag, tree_order_statistics_node_update> SuperTree;const ull mod = 1e9 + 7;const int INF = 0x7fffffff;SuperTree Tree, Tree_t;char cmd[2];int main(){#ifdef __AiR_H    freopen("in.txt", "r", stdin);#endif // __AiR_H    int n, Min;    scanf("%d%d", &n, &Min);    int Count = 0, Leave = 0;    int Add = 0;    for (int i = 0; i < n; ++i) {        ll k;        scanf("%s%lld", cmd, &k);        if (cmd[0] == 'I') {            if (k >= Min) {                k -= Add;                ++Count;                Tree.insert((k<<20) + i);            }        } else if (cmd[0] == 'A') {            Add += k;        } else if (cmd[0] == 'S') {            Add -= k;            ll low = Min - Add;            int leave_t = Count - Tree.order_of_key(low<<20);            Count -= leave_t, Leave += leave_t;            Tree.split(low<<20, Tree_t);    //按值分割        } else {            if (k <= Count) {                printf("%lld\n", (*Tree.find_by_order(k-1)>>20) + Add);            } else {                printf("-1\n");            }        }    }    printf("%d\n", Leave);    return 0;}


模板参数

更多用法





性能测试

测试一:平衡排序树



测试二:字典


hash_table

总结



1 0
原创粉丝点击