pb_ds 是c++中喜闻乐见的一个库,自从g++4.x版本以来,pb_ds就一直存在于ext/pb_ds这个目录下。
1.pb_ds 的基本组件
1. 简介
pb_ds 由 hash_table,trie,priority_queue,tree 4大部分组成.
2.hash_table
使用hashtable需要包含头文件assoc_container.hpp和hash_policy.hpp,基本格式为
template<typename Key,typename Mapped,typename Hash_Fn,typename Eq_Fn,typename Resize_Policy,bool Store_Hash,typename Tag,typename Policy_Tl,typename _Alloc>
class basic_hash_table : public PB_DS_HASH_BASE
通常这种形式不实用也不好用,我们一般使用另外两个定义,cc_hash_table和gp_hash_table。其中cc_hash_table使用拉链法解决冲突,
gp_hash_table使用探测法解决冲突。
template<typename Key,typename Mapped,
typename Hash_Fn = typename detail::default_hash_fn<Key>::type,
typename Eq_Fn = typename detail::default_eq_fn<Key>::type,
typename Comb_Hash_Fn = detail::default_comb_hash_fn::type,
typename Resize_Policy = typename detail::default_resize_policy<Comb_Hash_Fn>::type,
bool Store_Hash = detail::default_store_hash,typename _Alloc = std::allocator<char> >
class cc_hash_table : public PB_DS_CC_HASH_BASE
template<typename Key,typename Mapped,
typename Hash_Fn = typename detail::default_hash_fn<Key>::type,
typename Eq_Fn = typename detail::default_eq_fn<Key>::type,
typename Comb_Probe_Fn = detail::default_comb_hash_fn::type,
typename Probe_Fn = typename detail::default_probe_fn<Comb_Probe_Fn>::type,
typename Resize_Policy = typename detail::default_resize_policy<Comb_Probe_Fn>::type,
bool Store_Hash = detail::default_store_hash,
typename _Alloc = std::allocator<char> >
class gp_hash_table : public PB_DS_GP_HASH_BASE
其中key,mapped定义与stl中的map相同,如果使用set则mapped那里填null_type(旧版本编译器中写null_mapped_type),后面几个通常不需要改,也不知道怎么改。
使用时就当map/set用好了。一般如下定义:
typedef cc_hashmap_int cc_hash_table<int,int>
typedef cc_hashset_int cc_hash_table<int,null_mapped_type>
typedef gp_hashmap_int gp_hash_table<int,int>
typedef gp_hashset_int gp_hash_table<int,null_mapped_type>
3.tree
tree是pb_ds中很常用的一个组件,与stl中的set/map对应,但是功能更强大。使用tree要包含assoc_container.hpp和tree_policy.hpp。tree的基本定义如下:
template<typename Key, typename Mapped, typename Cmp_Fn = std::less<Key>,
typename Tag = rb_tree_tag,
template<typename Node_CItr, typename Node_Itr,typename Cmp_Fn_, typename _Alloc_>class Node_Update = null_node_update,typename _Alloc = std::allocator<char> > class tree : public PB_DS_TREE_BASE
我们通常要结合使用tree_order_statistics_node_update,才能有效发挥tree的强大性能。一般如下使用:
typedef int_kthset tree<int,null_mapped_type,less<int>,rb_tree_tag,tree_order_statistics_node_update>
typedef int_kthmap
tree<int,int,less<int>,rb_tree_tag,tree_order_statistics_node_update>
这时这个容器相当于一个支持查询rank和第k大的平衡树,有如下几种用法:(以set形态为例)
1.insert(key)
2.erase(key)
3.iterator find_by_order(int rank) //寻找第rank+1小元素的迭代器
4.int order_of_key(int key) //得到key的rank-1
5.join(tree &b) //将b中元素加入该树中,并清空b(建议只在定义相同情况下使用)
6.split(const int key,tree &b) //清空树b,并将该树(this)中大于key的元素移到b中
如果模板参数中less 换成 greater,则以上操作意义相反。
tree的功能十分强大,甚至我们可以自己编写node_update,实现更多功能。例如以下写法,就是接近于写了一个阉割版的线段树:
template<typename Node_CItr, typename Node_Itr, typename Cmp_Fn, typename _Alloc>
struct range_sum_node_update
{
virtual Node_CItr node_begin() const = 0;
virtual Node_CItr node_end() const = 0;
typedef int metadata_type;
inline void operator()(Node_Itr iter,Node_CItr null_iter)
{
Node_Itr l=iter.get_l_child(),r=it.get_r_child();
int left=0,right=0;
if(l!=null_iter) left=l.get_metadata();
if(r!=null_iter) right=r.get_metadata();
const_cast<metadata_type &>(it.get_metadata())=left+right+(*it)->second;
}
inline int sumx(int x)
{
int ans = 0;
Node_CItr iter=node_begin();
while (iter!=node_end())
{
Node_CItr l=iter.get_l_child(),r=iter.get_r_child();
if(Cmp_Fn()(x,(*iter)->first)) iter=l;
else {
ans+=(*iter)->second;
if(l!=node_end()) ans+=l.get_metadata();
iter=r;
}
}
return ans;
}
inline int sum(int l,int r)
{
return sumx(r)-sumx(l-1);
}
};
好吧,其实功能也就和树状数组差不多,都是利用前缀和求区间和,不过用起来十分简便。(ps:这时就必须用map形态了,第一个参数是位置,第二个是值)
tree<int,int,less<int>,rb_tree_tag,range_sum_node_update> tr;
tr[1]=1;tr[2]=2;tr[3]=3;tr[4]=4;
assert(tr.sumx(3)==6);
assert(tr.sum(2,4)==9);
所以,tree的潜能大着呢!
3.trie
trie就是大名鼎鼎的字典树了,字典树,顾明思议就是解决字符串查询问题的。使用trie要包含assoc_container.hpp和trie_policy.hpp。一般如下使用:
template<typename Key,typename Mapped,typename _ATraits = \typename detail::default_trie_access_traits<Key>::type,typename Tag = pat_trie_tag,
template<typename Node_CItr,
typename Node_Itr,
typename _ATraits_,
typename _Alloc_> class Node_Update = null_node_update,
typename _Alloc = std::allocator<char> > class trie : public PB_DS_TRIE_BASE
trie和tree一样,真正的功力都在node_update中,因而我们应该这样用:
typedef trie<string,null_type,trie_string_access_traits<>,
pat_trie_tag,trie_prefix_search_node_update> tried;
注意:第一个参数必须是string或者其他字符串类型,而且通常把trie弄成类似map的样子极其不方便,所以一般第二个参数都是置为空。
这时,tire可以支持的操作有:
1.insert(string s)
2.erase(string s)
3.join(tire& b)
4.pair
pair<tried::iterator,tried::iterator> range=base.prefix_range(x);
for(tired::iterator iter=range.first;it!=range.second;it++)
cout<<" "<<*it<<endl;
pair中第一个是起始迭代器,第二个是终止迭代器,遍历过去就可以找到所有字符串了。
既然有了trie,我们就可以实现简单的后缀树:(如下,查找一个字符串在另一个字符串中出现了多少次)
#include <bits/stdc++.h>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/trie_policy.hpp>
using namespace std;
using namespace __gnu_pbds;
typedef trie<
string,
null_type,
trie_string_access_traits<>,
pat_trie_tag,
trie_prefix_search_node_update>
pref_trie;
pref_trie t1;
int main()
{
string x;
cin>>x;x=x+"$";
for (int i=0;i<x.length();i++) t1.insert(x.substr(i));
string t;
cin>>t;
int n=0;
pair<pref_trie::iterator,pref_trie::iterator> range=t1.prefix_range(t);
for (pref_trie::iterator it=range.first;it!=range.second;it++) n++;
cout<<n;
return 0;
}