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


目录

  • 一、平衡树
    • 例题:luogu P3369 【模板】普通平衡树
    • 1. `rb_tree_tag`版
    • 2. `splay_tree_tag`版
    • 功能不够?自己添加!
    • 例题:CF459D Pashmak and Parmida's problem
  • 二、字典树
    • 例题:Astronomical Database
  • 三、hash

__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,是264−12^{64} - 1264−1,而数据是10710^7107。也就是小于2242^{24}224,我们左移20位,最多是244<264−12^{44}<2^{64} - 1244<264−1,我们左移20位加一个不大且都不相同的常数,这样使得整个数据都不相等,而且我们右移20位输出答案的时候,加的那个不相同的常数会被右移挤掉,所以原来的数是多少,左移20位加上一个常数(小于2202^{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;}elsebase.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 %%%";elsecout<<"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)!

参考:

  • PBDS学习笔记(一)

  • 谈c++ pb_ds库(二) 红黑树大法好

  • [洛谷日报第39期]比STL还STL?——pbds

  • STL浅析 RB-tree(红黑树)

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

  1. 手把手从零开始搭建k8s集群超详细教程

    本教程根据B站课程云原生Java架构师的第一课K8s+Docker+KubeSphere+DevOps同步所做笔记教程 k8s集群搭建超详细教程 1. 基本环境搭建 1. 创建私有网络 2. 创建服务 ...

  2. 二进制安装部署 4 kubernetes集群---超详细教程

    二进制安装部署kubernetes集群---超详细教程 前言:本篇博客是博主踩过无数坑,反复查阅资料,一步步搭建完成后整理的个人心得,分享给大家~~~ 本文所需的安装包,都上传在我的网盘中,需要的可以 ...

  3. 【超详细教程】使用Windows Live Writer 2012和Office Word 2013 发布文章到博客园全面总结...

    [超详细教程]使用Windows Live Writer 2012和Office Word 2013 发布文章到博客园全面总结 原文 http://www.cnblogs.com/liuxianan/ ...

  4. 微服务Springcloud超详细教程+实战(二)

    微服务Springcloud超详细教程+实战(二) -------------------------------------- 远程调用方式 无论是微服务还是分布式服务(都是SOA,都是面向服务编程 ...

  5. 【K8S实战】-超详细教程(二)

    [K8S实战]-超详细教程(二) 环境这块的这里我就不过多描述了,需要了解的可以看这篇文章[k8s搭建(超详细,保姆级教程)]. 1.Deployment Deployment其他功能我上一篇文章已写 ...

  6. 【K8S实战】-超详细教程(三)

    [K8S实战]-超详细教程(三) 1.存储 1.1.nfs默认存储 我这里只演示nfs作为K8S的默认存储,其他的可以看这里[存储类]. 1.1.1.安装nfs服务 所有机器都安装nfs工具 所有机器 ...

  7. AD从原理图到PCB超详细教程

    AD超详细教程 前言 一.建立一个工程模板 二.原理图 1.设计原理图. 2.使用AD自带库和网上开源原理图库 3.画原理图库 4.编译原理图 三.PCB 1.确定元器件尺寸大小 2.绘制PCB Li ...

  8. 10分钟教你用python打造贪吃蛇超详细教程

    更多精彩尽在微信公众号[程序猿声] 10分钟教你用python打造贪吃蛇超详细教程 在家闲着没妹子约, 刚好最近又学了一下python,听说pygame挺好玩的.今天就在家研究一下, 弄了个贪吃蛇出来 ...

  9. d3.js画柱状图超详细教程

    d3.js画柱状图超详细教程 完整代码下载链接:https://download.csdn.net/download/qq5q13638/85248934,直接用这个文件夹内打开即可. 下面是完整教程 ...

  10. 大疆无人机空三建模干货分享(大疆智图集群建模超详细教程)

    Part 01 大疆无人机空三建模干货分享(大疆智图集群建模超详细教程) 大疆智图集群简介 大疆在今年5月推出的智图3.0.0及以上版本中加入了集群功能,有集群版许可的用户可以使用此功能.智图集群是由 ...

最新文章

  1. [CES 2018] TPCast发布升级版本,将支持微软MR设备
  2. shell实例第8讲:seq命令
  3. linux 多线程实现倒计时,Linux用脚本实现“时分秒“倒计时功能
  4. aspx,ascx和ashx使用小结
  5. nginx应用领域分类+事件模型
  6. 405 Method Not Allowed
  7. Linux crontab 定时任务没执行,没收到错误信息邮件
  8. 【线性代数】 解行列式的基本方法
  9. 多进程修改全局变量(python版)
  10. 关于物联网规则引擎技术,你想要知道的都在这儿!
  11. 格力:核心科技有时也是高利贷
  12. Top 10 盘点:2019 Java 开发者必学的测试框架、工具和库!
  13. Linux下修改PATH的方法
  14. 下载kaggle数据集
  15. 黑龙江省鹤岗市谷歌高清卫星地图下载
  16. u盘中毒后文件夹被病毒隐藏
  17. matlab set函数
  18. 错过了前三次的伟大革命(蒸汽机革命、电气革命、信息革命),GPT-4 你还要错过人工智能AI革命吗!!!
  19. 女子上班10天被辞退获赔1万,不料竟收到122斤硬币,法院做法亮了
  20. 永久の思い出 --- 玉玲 命中の美しいMELODY

热门文章

  1. 如何利用深度学习知识--快速部署高速目标检测智能小车?
  2. 图像分割20年,盘点影响力最大的10篇论文
  3. 关于如何换肤、子类化的解决方案
  4. 回溯算法——算法总结(四)
  5. Linux Ubuntu从零开始部署web环境及项目 -----tomcat+jdk+mysql (二)
  6. 【更新】比较智能的爬取姓名
  7. 64位ubuntu安装WPS
  8. WCF .net Tcp 错误异常
  9. sklearn gridcv
  10. html5移动站点是什么,浅谈移动站点优化之痛-HTML5 什么是html5?