首页 > 编程语言 >C++STL进阶:pb_ds库

C++STL进阶:pb_ds库

时间:2023-09-16 23:23:10浏览次数:48  
标签:std insert 12 进阶 get STL tree pb ++

Windows, 64bit
G++ (ISO c20)
stack=268435456
开启O2优化

万能头文件

CodeForces在 \(\tt C^{20(64)}_{++}\) 版本下无法使用 bits;如果需要使用 priority_queue 则无法使用 using(会和 std 撞名字)。

#include <bits/extc.h>
using namespace __gnu_pbds;

优先队列(不常用)

概述

一般使用 pairing_heap_tag,速度最快;binary_heap_tag 也大致能够接受。两者均拥有 std::priority_queue 的性质和函数,默认从大到小排序。

__gnu_pbds::priority_queue<int> p; // 标准写法
__gnu_pbds::priority_queue<int, greater<int>, __gnu_pbds::pairing_heap_tag> q; // 转换为小根堆

迭代器的使用

允许使用迭代器,输出的是在堆里的状态(注意,并不是默认的从小到大)。

__gnu_pbds::priority_queue<int> p;
p.push(12);
p.push(14);
p.push(14);
for (auto it : p) {
    cout << it << " "; // 输出:14 12 14
}

复杂度

结论:时间复杂度差于 std::priority_queue

  • CodeForces在 \(\tt C^{20(64)}_{++}\) 版本下使用 std::priority_queue 优化 djikstra 耗时124ms,使用 __gnu_pbds::priority_queue耗时156ms;使用 \(\tt C^{17(64)}_{++}\) 版本耗时更长,为 171ms
  • Atcoder在 \(\tt C^{20(gcc 12.2)}_{++}\) 版本下基本同上。

平衡树(功能最多)

概述

一般使用 rb_tree_tag,拥有 std::set 的性质和函数,速度较快。

tree<int, null_type, less<int>, rb_tree_tag> p; // 标准写法

p.insert(12);
p.insert(12);
p.insert(19);
p.insert(2);
for (auto it : p) {
    cout << it << " "; // 输出:2 12 19
}

关于第二参数

其中第二个参数如果不写内置关键字 null_type,则能够实现 std::map 的功能,该参数即等同于 std::map 的第二参数(但是效率巨低无比)。

tree<int, int, less<int>, rb_tree_tag> p;

p[12]++;
p[12]++;
p[19]++;
p[2]++;
for (auto [a, b] : p) {
    cout << a << " " << b << endl;
}
/* 输出:
2 1
12 2
19 1
*/

如果第二关键字为 char 等其他类型,则需要手动书写一个哈希,否则输出会出现问题。

关于第三参数

第三个参数可以使用 _equal 后缀以允许插入重复元素,进而替代 std::multiset

tree<int, null_type, less<int>, rb_tree_tag> P;
tree<int, null_type, less_equal<int>, rb_tree_tag> p;

P.insert(12), p.insert(12);
P.insert(12), p.insert(12);
P.insert(19), p.insert(19);
P.insert(2), p.insert(2);
for (auto it : P) {
    cout << it << " ";
}
cout << endl;
for (auto it : p) {
    cout << it << " ";
}
/* 此时:
P = [2 12 19]
p = [2 12 12 19]
*/

在此操作之后,函数的性质发生了一些变化,find 函数不再能正确使用。

if (P.find(12) != P.end()) {
    cout << "AC!\n";
}
if (p.find(12) == p.end()) {
    cout << "WA!\n";
}
/* 输出:
AC!
WA! 
*/

lower_boundupper_bound 函数的实现将会被翻转:

  • lower_bound(x):找到 \(> x\) 的第一个迭代器;
  • upper_bound(x):找到 \(\ge x\) 的第一个迭代器。
cout << *P.upper_bound(12) << " " << *P.lower_bound(12) << endl;
cout << *p.upper_bound(12) << " " << *p.lower_bound(12) << endl;
/* 输出:
19 12
12 19 
*/

关于第五参数

其特殊之处在于最后一个可选参数类:内置关键字 tree_order_statistics_node_update 可以同步记录子树大小,使得你可以使用 find_by_order()order_of_key() 函数。

tree<int, null_type, less<int>, rb_tree_tag, tree_order_statistics_node_update> p;

手写可选参数类

可以手写函数实现不同的功能,大模板如下:

template<class Node_CItr, class Node_Itr, class Cmp_Fn, class _Alloc>
struct myNodeUpdate {
    virtual Node_CItr node_begin() const = 0;
    virtual Node_CItr node_end() const = 0;
    typedef int metadata_type;
};

可以重载 operator() 函数,将节点的信息更新为其两个子节点的信息之和。常用函数展示:

  • get_l_child:获取左儿子;
  • get_r_child:获取右儿子;
  • get_metadata:获取节点额外信息。

下方展示一个计算子节点值之和的例子,可以类比于求解 std::map 前缀和、区间之和:

template<class Node_CItr, class Node_Itr, class Cmp_Fn, class _Alloc>
struct myNodeUpdate {
    virtual Node_CItr node_begin() const = 0;
    virtual Node_CItr node_end() const = 0;
    typedef int metadata_type; // 声明节点上记录的额外信息的类型

    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();
        // (*it)->second 为当前节点 it 的 mapped_value
        const_cast<metadata_type &>(it.get_metadata()) = left + right + (*it)->second;
    }
    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;
    }
    int interval_sum(int l, int r) {
        return prefix_sum(r) - prefix_sum(l - 1);
    }
};

测试结果如下:

tree<int, int, less<int>, rb_tree_tag, myNodeUpdate> p;
p[2] = 100;
p[3] = 1111;
p[9] = 1;
cout << p.prefix_sum(-1) << endl;
cout << p.prefix_sum(8) << endl;
cout << p.interval_sum(-500, 99) << endl;
cout << p.size() << endl;
/* 输出:
0
1211
1212
3
*/

复杂度

个人尚未做过相关测试,引用论文中图表:

哈希表(优化较大)

概述

一般使用 gp_hash_table,速度较快(使用查探法哈希);cc_hash_table 速度稍慢(使用拉链法哈希),但均快于 mapunordered_map

gp_hash_table<int, int> dic; // 标准写法

struct myhash {
    static uint64_t splitmix64(uint64_t x) {
        x += 0x9e3779b97f4a7c15;
        x = (x ^ (x >> 30)) * 0xbf58476d1ce4e5b9;
        x = (x ^ (x >> 27)) * 0x94d049bb133111eb;
        return x ^ (x >> 31);
    }
    size_t operator()(uint64_t x) const {
        static const uint64_t FIXED_RANDOM =
            chrono::steady_clock::now().time_since_epoch().count();
        return splitmix64(x + FIXED_RANDOM);
    }
};
gp_hash_table<int, int, myhash> dic; // 支持自定义随机哈希种子

复杂度

结论:时间复杂度优于 mapunordered_map

  • CodeForces在 \(\tt C^{20(64)}_{++}\) 版本下使用 std::map 耗时920ms,使用自定义随机哈希种子 std::unordered_map 耗时842ms;使用 gp_hash_table 耗时764ms;使用自定义随机哈希种子 gp_hash_table 耗时780ms

标签:std,insert,12,进阶,get,STL,tree,pb,++
From: https://www.cnblogs.com/WIDA/p/17707514.html

相关文章

  • 《STL源码剖析》 - 侯捷 2002年
    我们的第一个c++stlapplication。什么是容器?什么是迭代器?什么是算法?什么是分配器?什么是适配器?什么是仿函式?1.容器就是装数据的容器,等于是数据结构?不应该吧?数据结构不应该是自定义的吧?为什么说容器是数据结构搞不懂。2.分配器,用于给容器分配内存。3.迭代器,用于从容器中......
  • The 2nd Universal Cup. Stage 2: SPb
    链接:https://contest.ucup.ac/contest/1356A.MixedMessages#include"bits/stdc++.h"usingnamespacestd;usingi64=longlong;intmain(){ios::sync_with_stdio(false);cin.tie(nullptr);intn;strings;cin>>n......
  • 合并果子题解-C++ STL priority_queue容器的使用
    说明:本博文关于priority_queue容器的说明来源于www.cnblogs.com/fusiwei/p/11823053.html本人是刚刚接触算法竞赛的萌新,如果有大佬发现了错误,还望指出(真的有人会看本蒟蒻的博文吗)这是我的第一篇博文,更多是作为测试以后会将博客作为笔记记录学习的体会基本概念priority_queu......
  • k8s安装Dashboard出现了 pod 状态为CrashLoopBackOff
    1、问题现象2、解决办法(1)先看一下pods日志信息kubectllogs-f-nkubernetes-dashboardkubernetes-dashboard-658485d5c7-h75rs(2)错误信息:Get"https://10.96.0.1:443/api/v1/namespaces/kubernetes-dashboard/secrets/kubernetes-dashboard-csrf":dialtcp10.9......
  • STL(上)
    STL基本概念STL(StandardTemplateLibrary,标准模板库)STl从广义分为:容器(container)、算法(algorithm)、迭代器(iterator)容器和算法之间通过迭代器进行无缝链接STL几乎所有的代码都采用了模板类或者模板函数STL六大组件1.容器----各种数据结构,如vector、list、deque、set、map等用......
  • C++ STL 编程指北
    C++STL编程指北未避免歧义,所有容器的swap方法和不常用方法均未写1.vector向量容器用一句话来说,vector就是可变长数组。但vector所支持的可变长特性,并不是在原空间之后接续新空间来实现的,因为无法保证之后尚有可供分配的空间。底层实现上当增加新元素时,如果当前vector容......
  • mapbox点图层标注根据zoom层级进行显示与隐藏
    主要使用了这个表达式进行过滤:"text-opacity":["step",["zoom"],0,5,1]这个表达式的意思就是zoom在小于5时text-opacity值等于0,大于5时text-opacity值等于1constaddPortsGeoJSONLayer=(ports)=>{letmap=G.map;map.loadImage(portIcon,function(error,im......
  • 不知道如何入门Kotlin?《Android版kotlin协程入门进阶实战》带你从入门,带你飞
    作为一名Android开发者,掌握Kotlin语言对于职业发展具有重要意义。随着Google正式将Kotlin确立为Android开发的官方编程语言,Kotlin的地位在Android开发领域迅速攀升。因此,仅仅依靠Java语言进行开发已经不能满足当前市场需求。作为一名Android开发者,学习和掌握Kotl......
  • Vue进阶(幺柒肆):鼠标、键盘事件
    (文章目录)一、前言在项目开发过程中,需要根据鼠标事件进行相应处理。现予以梳理。鼠标事件如下所示:点击事件:@click//单击@dblclick//双击@mousedown//按下@mouseup//抬起@contextmenu//鼠标右键悬浮事件及触发顺序:@mouseover//划过@mouseenter//进入@mouse......
  • 疯踏java知识点-进阶精讲篇
    。继续进行讲解,如果前面有不懂的,可以翻阅一下同专栏的其他文章,该专栏是针对Java的知识从0开始。JavaBean一个Java中的类,其对象可用于程序中封装数据举例:学生类,手机类要求:1、成员变量使用private修饰2、提供每一个成员变量对应的setXxx()/getXxx()......