整理的算法模板合集: 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 264−1,而数据是 1 0 7 10^7 107。也就是小于 2 24 2^{24} 224,我们左移20位,最多是 2 44 < 2 64 − 1 2^{44}<2^{64} - 1 244<264−1,我们左移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
有以下的操作:
-
get_l_child()
,返回其左孩子的迭代器,没有则返回node_end; -
get_r_child()
,同get_l_child();
-
get_metadata()
,返回其在树中维护的数据; -
**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
#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)!
参考: