C++ __gnu_pbds(平板电视)超详细教程(C++内置的平衡树,字典树,hash)

整理的算法模板合集: ACM模板


__gnu_pbds自带了封装好了的平衡树、字典树、hash等强有力的数据结构,常数还比自己写的小,效率更高hhh

一、平衡树

#define PII pair<int, int>
#define mp_(x, y) make_pair(x, y)
tree<int, null_type, less<int>, rb_tree_tag, tree_order_statistics_node_update> tr;
/// rb_tree_tag 和 splay_tree_tag 选择树的类型(红黑树和伸展树)
null_type//无映射(g++为null_mapped_type)
less<PII>//从小到大排序
tree_order_statistics_node_update//更新方式

tr.insert(mp_(x, y));//插入
tr.erase(mp_(x, y));//删除
tr.order_of_key(PII(x, y));//求排名
tr.find_by_order(x);//找k小值,返回迭代器
tr.join(b);//将b并入tr,前提是两棵树类型一致并且二没有重复元素
tr.split(v, b);//分裂,key小于等于v的元素属于tr,其余属于b
tr.lower_bound(x);//返回第一个大于等于x的元素的迭代器
tr.upper_bound(x);//返回第一个大于x的元素的迭代器
//以上的所有操作的时间复杂度均为O(logn)
//注意,插入的元素会去重,如set

//显然迭代器可以++,--运算

例题:luogu P3369 【模板】普通平衡树

在这里插入图片描述
hhh,pbds的平衡树比我之前写的无旋treap都快

在这里插入图片描述

1. rb_tree_tag

因为tree里不能有相同的数,但是实际会插入相同的数,所以把这些数左移20位在加上一个常数操作,这样就保证了在不影响相对大小关系的情况下,消除了相同的数,因为是long long,是 2 64 − 1 2^{64} - 1 2641,而数据是 1 0 7 10^7 107。也就是小于 2 24 2^{24} 224,我们左移20位,最多是 2 44 < 2 64 − 1 2^{44}<2^{64} - 1 244<2641,我们左移20位加一个不大且都不相同的常数,这样使得整个数据都不相等,而且我们右移20位输出答案的时候,加的那个不相同的常数会被右移挤掉,所以原来的数是多少,左移20位加上一个常数(小于 2 20 2^{20} 220)再右移,这个数是不会变的,还成功完成了去重!!!。

//#pragma GCC optimize("Ofast")
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
//#include<bits/extc++.h>//codeblocks不让用呜呜呜
#include<ext/rope>
#include<ext/pb_ds/assoc_container.hpp>
#include<ext/pb_ds/tree_policy.hpp>

#define debug(x) cout << x << "ok" << endl
typedef long long ll;
#define file freopen("1.in", "r", stdin);freopen("1.out", "w", stdout);
const int N = 2e5 + 7, M = 1e5 + 7, INF = 0x3f3f3f3f;

using namespace std;
using namespace __gnu_cxx;
using namespace __gnu_pbds;

ll read()
{
    ll x = 0, f = 1;char ch = getchar();
    while(ch < '0' || ch > '9'){if(ch == '-')f = -1;ch = getchar();}
    while(ch >= '0' && ch <= '9'){x = x * 10 + ch - '0';ch = getchar();}
    return x * f;
}

tree<ll, null_type, less<ll>, rb_tree_tag, tree_order_statistics_node_update> tr;
int n, m;
ll k, ans;

int main()
{
    n = read();
    for(int i = 1; i <= n; ++ i){
        int op = read();
        k = read();
        if(op == 1)tr.insert((k << 20) + i);
        if(op == 2)tr.erase(tr.lower_bound(k << 20));
        if(op == 3)printf("%d\n", tr.order_of_key(k << 20) + 1);
        if(op == 4)ans = *tr.find_by_order(k - 1), printf("%lld\n", ans >> 20);
        if(op == 5)ans = *-- tr.lower_bound(k << 20), printf("%lld\n", ans >> 20);
        if(op == 6)ans = *tr.upper_bound((k << 20) + n), printf("%lld\n", ans >> 20);
    }
    return 0;
}

2. splay_tree_tag

本来想展示一下pbds解法的,被人抢先一天,不过不要紧,我用另一种写法,修改数值不太普遍,不如pair<int,int>+map时间戳,没有用rb_tree_tag,那样会更快,但是splay_tree_tag更安全。没有使用Lowerbound而是用order_of_key等操作,大家可以学习一下用法,注意Off-by-one
mistake

来源

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <map>
#include <ext/pb_ds/assoc_container.hpp> 
using namespace std;
using namespace __gnu_pbds;
#define Node pair<int,int>
map <int,int> s;
tree< Node ,null_type,less< Node >,splay_tree_tag,tree_order_statistics_node_update> T;
int n,op,x;
int main()
{
    scanf("%d",&n);
    for(register int i = 1; i <= n; i++)
        switch(scanf("%d%d",&op,&x), op)
        {
            case 1 :T.insert(Node(x,s[x]++));
                    break;
            case 2 :T.erase(Node(x,--s[x]));
                    break;
            case 3 :printf("%d\n",(int)T.order_of_key(Node(x,0))+1);
                    break;
            case 4 :printf("%d\n",T.find_by_order(x-1)->first);
                    break;
            case 5 :printf("%d\n",T.find_by_order(
                    T.order_of_key(Node(x,0))-1
                                                      )->first);
                    break;
            case 6 :printf("%d\n",T.find_by_order(
                    T.order_of_key(Node(x,s[x]-1))+(T.find(Node(x,0)) == T.end() ? 0 : 1)
                                                      )->first);
                    break;
            default:break;
        }
    return 0;
}

底层代码

功能不够?自己添加!

我们可以自己定义更新的各种方式
我们需要写一个自己的node_update(定义tree的时候使用自己定义的update即可):

template<class Node_CItr,class Node_Itr,class Cmp_Fn,class _Alloc>
struct my_node_update
{
    typedef my_type metadata_type;
    void operator()(Node_Itr it, Node_CItr end_it)
    {
        ...
    }
};

我们先解释一下这个类是如何工作的。节点更新的tree都会保存一个my_type类型的变量。当我们修改这棵树的时候,会从叶子节点开始修改,并且每次都会调用operator(),我们来看一下这个函数的两个参数:

Node_Itr it为调用该函数的元素的迭代器,Node_CItr end_it可以const到叶子节点的迭代器,Node_Itr有以下的操作:

  1. get_l_child(),返回其左孩子的迭代器,没有则返回node_end;

  2. get_r_child(),同get_l_child();

  3. get_metadata(),返回其在树中维护的数据;

  4. **it可以获取it的信息。

为了详细讲解,我们举一个更新子树大小的例子:

template<class Node_CItr, class Node_Itr, class Cmp_FN, class _Alloc>
struct my_node_update
{
    typedef my_type metadata_type;
    //my_type就是指的自己用的类型,例如int,double。。。
    
    //更新子树大小
    void operator()(Node_Itr it, Node_CItr end_it)
    {
        Node_Itr l = it.get_l_child();
        Node_Itr r = it.get_r_child();
        int left = 0,right = 0;
        if(l !=end_it) left = l.get_metadata();//只要有左子节点,就得到左子节点元素大小
        if(r != end_it) right = r.get_metadata();//只要有右子节点,就得到右子节点元素大小
        //const_cast标准准换运算符,用于修改一个const常量
        const_cast<int&>(it.get_metadata()) = left + right + 1;
    }
};

现在我们学会了更新,那么我们该如何自己写操作呢?node_update所有public方法都会在树中公开。如果我们在node_update中将它们声明为virtual,则可以访问基类中的所有virtual。所以,我们在类里添加以下内容:

virtual Node_CItr node_begin() const=0;
virtual Node_CItr node_end() const=0;

这样我们就能直接访问树了,还有,node_begin指向树根,node_end指向最后一个叶子节点的后一个地址,下面这个就是查排名的操作:

int myrank(int x)
{
    int ans=0;
    Node_CItr it=node_begin();
    while(it!=node_end())
    {
        Node_CItr l=it.get_l_child();
        Node_CItr r=it.get_r_child();
        if(Cmp_Fn()(x,**it))
            it=l;
        else
        {
            ans++;
            if(l!=node_end()) ans+=l.get_metadata();
            it=r;
        }
    }
    return ans;
}

template < class Node_CItr , class Node_Itr ,
    class Cmp_Fn , class _Alloc  >
struct my_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 it , Node_CItr end_it ){
        Node_Itr l = it. get_l_child (), r = it. get_r_child ();
        int left = 0, right = 0;
        if(l != end_it ) left = l. get_metadata ();
        if(r != end_it ) right = r. get_metadata ();
        const_cast < metadata_type &>( it. get_metadata ())
            = left + right + (* it)-> second ;
    }
    //平衡树上二分
    inline int prefix_sum (int x) {
        int ans = 0;
        Node_CItr it = node_begin ();
        while (it != node_end ()) {
            Node_CItr l = it. get_l_child (), r = it. get_r_child ();
            if( Cmp_Fn ()(x, (* it)-> first )) it = l;
            else {
                ans += (* it)-> second ;
                if(l != node_end ()) ans += l. get_metadata ();
                it = r;
            }
        }
        return ans;
    }
    inline int interval_sum (int l, int r){
        return prefix_sum (r) - prefix_sum (l - 1);
    }
}
int main() {
    tree <int , int , std :: less <int >, rb_tree_tag , my_node_update > T;
    T [2] = 100; T [3] = 1000; T [4] = 10000;
    printf ("%d\n", T. interval_sum (3, 4));
    printf ("%d\n", T. prefix_sum (3));
} 

例题:CF459D Pashmak and Parmida’s problem

标答应该是先离散化,然后求前后缀和预处理,之后用树状数组求一下逆序对。

#include<bits/stdc++.h>
#include<ext/pb_ds/assoc_container.hpp>
#include<ext/pb_ds/tree_policy.hpp>

using namespace std;
using namespace __gnu_pbds;
typedef long long ll;
ll read()
{
    ll x = 0, f = 1;char ch = getchar();
    while(ch < '0' || ch > '9'){if(ch == '-')f = -1;ch = getchar();}
    while(ch >= '0' && ch <= '9'){x = x * 10 + ch - '0';ch = getchar();}
    return x * f;
}

template<class Node_CItr,class Node_Itr,class Cmp_Fn,class _Alloc>
struct my_node_update
{
    typedef int metadata_type;
    int order_of_key(pair<int,int> x)
    {
        int ans=0;
        Node_CItr it=node_begin();
        while(it!=node_end())
        {
            Node_CItr l=it.get_l_child();
            Node_CItr r=it.get_r_child();
            if(Cmp_Fn()(x,**it))
                it=l;
            else
            {
                ans++;
                if(l!=node_end()) ans+=l.get_metadata();
                it=r;
            }
        }
        return ans;
    }
    void operator()(Node_Itr it, Node_CItr end_it)
    {
        Node_Itr l=it.get_l_child();
        Node_Itr r=it.get_r_child();
        int left=0,right=0;
        if(l!=end_it) left =l.get_metadata();
        if(r!=end_it) right=r.get_metadata();
        const_cast<int&>(it.get_metadata())=left+right+1;
    }
    virtual Node_CItr node_begin() const = 0;
    virtual Node_CItr node_end() const = 0;
};
tree<pair<int,int>,null_type,less<pair<int,int> >,rb_tree_tag,my_node_update> me;
int main()
{
    map<int,int> cnt[2];
    int n;
    cin>>n;
    vector<int> a(n);
    for(int i=0;i<n;i++)
        cin>>a[i];
    vector<int> pre(n),suf(n);
    for(int i=0;i<n;i++)
    {
        pre[i]=cnt[0][a[i]]++;
        suf[n-i-1]=cnt[1][a[n-i-1]]++;
    }
    long long ans=0;
    for(int i=1;i<n;i++)
    {
        me.insert({pre[i-1],i-1});
        ans+=i-me.order_of_key({suf[i],i});
    }
    cout<<ans<<endl;
}

二、字典树

trie即为字典树,我们先看如何定义一个trie与它的操作:

typedef trie<string,null_type,trie_string_access_traits<>,pat_trie_tag,trie_prefix_search_node_update> tr;
//第一个参数必须为字符串类型,tag也有别的tag,但pat最快,与tree相同,node_update支持自定义
tr.insert(s); //插入s 
tr.erase(s); //删除s 
tr.join(b); //将b并入tr 
pair//pair的使用如下:
pair<tr::iterator,tr::iterator> range=base.prefix_range(x);
for(tr::iterator it=range.first;it!=range.second;it++)
    cout<<*it<<' '<<endl;
//pair中第一个是起始迭代器,第二个是终止迭代器,遍历过去就可以找到所有字符串了。 

例题:Astronomical Database

Astronomical Database
在这里插入图片描述

#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;
int main()
{
    pref_trie base;
    base.insert("sun");
    string x;
    while(cin>>x)
    {
        if(x[0]=='?')
        {
            cout<<x.substr(1)<<endl;
            auto range=base.prefix_range(x.substr(1));
            int t=0;
            for(auto it=range.first;t<20 && it!=range.second;it++,t++)
                cout<<"  "<<*it<<endl;
        }
        else
            base.insert(x.substr(1));
    }
}

三、hash

hash_table的用法与map类似,它是这么定义的:

cc_hash_table<int,bool> h;
gp_hash_table<int,bool> h;

其中cc开头为拉链法,gp开头为探测法,个人实测探测法稍微快一些。

操作和map差不多,支持[ ]和find。

#include<bits/stdc++.h>
#include<ext/pb_ds/assoc_container.hpp>
#include<ext/pb_ds/hash_policy.hpp>
using namespace std;
using namespace __gnu_pbds;
gp_hash_table<string,int> h;
void judge(string s)
{
    if(h.find(s)!=h.end())
        cout<<"orz %%%";
    else
        cout<<"tan90";
    cout<<endl;
}
int main()
{
    h["Ican'tAKIOI"]=1;
    h.insert(make_pair("UAKIOI",1));
    string str;
    while(cin>>str)
        judge(str);
    return 0;
}

注意:hash_table的总时间复杂度仅为 O ( n ) O(n) O(n)

参考:

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 撸撸猫 设计师:设计师小姐姐 返回首页