STL 容器以及各函数及其复杂度

主要是实质、函数、性质。
如果没有特殊提出,那么是常数复杂度。

vector

相当于变长数组。

  • 支持随机访问(可以直接查询 \(v_i\)),但是不任意位置 \(O(1)\) 插入。通常为了保证效率,只在末尾加入删除元素。
  • size:返回实际长度(通常声明的长度会大于实际长度),也就是包含的元素个数。
  • empty:判断是否为空,是返回 \(1\),不是返回 \(0\)
  • clear:直接清空。时间复杂度线性。
  • 迭代器:随机访问迭代器(注意),可以直接进行加减操作(作差、求和也可)。
  • begin/end:返回第一个和最后一个函数的迭代器,如果想返回值的话,可以用 *v.begin (),与数组的 \(a_0\) 效果相同。注意下标是从零开始,所以 *a.end () 是越界访问。
  • front/back:返回第一个或最后一个元素,即 a.front () = *a.begin ()a.back () = *--a.end ()
  • push_back/pop_back:在末尾插入或者删除元素。

可以代替邻接表存边,如果想连一条 \(x\)\(y\) 的有向边,那么只需:

v[x].push_back (y) ;

无向边同理。

queue

FIFO,从队尾进入,队头出去。

  • push:入队。
  • pop:出队。
  • front:询问队头元素。
  • back:询问队尾元素。

用于 SPFA 和其他的什么东西,只不过快死掉就是了。

priority_queue

默认是一个大根二叉堆(其实不能算队列,但是它叫 queue,那就算到这里面好了)。

  • 相比 queue 多了一个 top 函数,可以查询堆顶元素(也就是最大值)。
  • 支持重载小于号。
  • 也可以实现小根堆,有这么两个方法:
    1.插入原数的相反数,这个方法容易废。
    2.priority_queue < int ,vector < int > ,greater < int > > q

deque

双端队列,相当于一个头朝左和一个头朝右的队列混到了一起,也可以看做一个 queue 和 vector 的结合。

  • 相比 queue,它支持随机访问。
  • 除了 front 和 back,它具有 begin/end 两个头、尾迭代器。
  • 不仅有 push_back 和 pop_back,还有 push_front 和 pop_front(因为是双端)。
  • 线性的 clear。

有人用这个玩意写单调队列,但是 STL 让人感觉不靠谱,不如手写。

set

只要你学过高中数学(其实不学也知道),就会知道 set(集合)具有互异性,也就是这个玩意可以自动帮你去重,但是他们同时是有序的默认可以单增排序,所以不具有无序性。

  • size/empty/clear 同 vector。
  • 迭代器:是双向访问迭代器,支持随机访问,只能 ++ 或者 --
  • begin/end 同 vector。
  • insert:插入操作,复杂度 \(\mathcal{O}(\log n)\),所以要是插入量大有可能炸掉,但是几率不很大。
  • find:在本集合中寻找是否有给定值,要是有返回迭代器,否则返回 s.end (),仍然带一个 \(\log\)
  • lower(upper)_bound:形式稍有不同,只需 s.lower(upper)_bound (x) 即可返回迭代器。
  • erase:如果给出的是迭代器,就删除迭代器指向位置的元素(\(\mathcal{O}(\log n)\)),如果给出的是值,那么会删除等于这个值的元素(\(\mathcal{O}(k+\log n)\),其中 \(k\) 是被删除的元素个数),在 set 下等效,但是到 multimap 就不一样了。这个玩意好用,但是复杂度让人隐隐约约不太放心。
  • count:返回集合中等于给定值的元素的个数,\(\mathcal{O}(k+\log n)\)

multiset

与 set 大致相同,但是允许重复。
唯一需要注意的是 erase,如果你给的是迭代器当然没有问题,但是如果是数值就需要注意了,因为它可能会删好几个。

map

不是地图,是映射。
其形式为:

map < key_type ,value_type > m ;

通常是把一个比较复杂的(如字符串)键值映射到比较简单的域上(如整数)。

  • size/empty/clear/begin/end/迭代器/insert/erase/find:同 set。
  • map 支持随机访问,这是最吸引人的地方。

但是由于它自带一个 \(\log\),可能会被爆破,比如我。

unordered_map

这个东西不像 map 一样有序,但是 hash 很喜欢它。
在单个查找的时候比 map 优秀,平均是常数复杂度。

bitset

可以 \(n^2\) 过十万?
顾名思义,是按 bit 存而不是 byte。
复杂度是 \(\mathcal {O}(\frac{n}{w})\),其中 \(w=32\)

一些函数

  • reverse 翻转,用处不大。
  • unique 去重,用来离散化,用处也不很大?形式:
int m = unique (a + 1 ,a + n + 1) - (a + 1) ; 

返回的是去重后的元素个数

  • random_shuffle 随机打乱,更没用了。
  • sort 有用但是常见,所以不说。
  • lower(upper)_bound 在这里说过了。
  • distance 这个值得说一说。目测出来是距离,需要给出两个参数 from 和 to,对数组来说:
int a[] = {...} ;
auto _ = std :: begin (a) ;
auto __ = std :: end (a) ;
auto count = distance (_ ,__) ;

对于容器来说(拿 vector 举例):

distance (v.begin () ,v.end ()) ;

但是对于 STL 这个函数用到不同的容器上复杂度不同,为肾摸?
不难看出这个函数的原理是计算两个迭代器之间的距离,所以不同种类的迭代器效率不同。
拿 vector 的随机访问迭代器举例,他可以直接进行加减操作,所以复杂度是 \(\mathcal{O}(1)\)
但是如果是像 set 这样的双向访问迭代器的话,这个迭代器只能疯狂地不停自增或者自减 \(1\),那么复杂度就退化为 \(\mathcal{O}(n)\)

STL 大法好!

set 直接 \(\mathcal{O}(n\log n)\) 爆过,只要你的数据范围在 \(5\times 10^6\) 以下完全没问题。栗子:

Tomoyuki-Mizuyama 的班级里共有 \(n\)\(1\le n\le10^6\))个同学,每个同学学号为 \(a_i\),现在他们排成一排准备入场看电影,老师给他们安排了一排座位,恰好为 \(n\) 个,编号从 \(0\)\(n-1\),现在从第 \(1\) 个同学开始,同学们按顺序入座,第 \(i\) 个同学的位置规则如下:

  • 若 (\(a_i\bmod n\)) 编号的座位为空,则第 \(i\) 个同学入座。
  • 若不为空,则从编号为 (\(a_i\bmod n\))的座位开始往右找到第一个空的座位坐下,如果到 \(n-1\) 号座位都不是空的,则从编号为 \(0\) 开始继续往右找。

现在 TM 想知道每个座位上坐的同学的学号分别是多少。

我知道正解是并查集,\(\mathcal{O}(n\log*n)\) 这个近似线性复杂度更快,但是用 set 这题仍然绰绰有余。
首先注意到每个人肯定是有位置的(废话),那么我们只要考虑如何去找就行了。那么(根据题目)设 \(ans_i\) 表示第 \(i\) 个位置上做的学生编号,那么如何实现呢?
我们考虑到这题座位编号不可能重复,而且座位是升序的,所以自然想到了 set。
首先我们对于学生 \(i\) 查找编号为 \(a_i\bmod n\) 的座位:

  • 如果没有人坐,直接落座,并且把这个座位从 set 中删除,这可以避免之后重复计算,用到了 erase 函数。
  • 有人坐(那么说明这个位置已经被删除了,我们用 find 函数就查找不到):接下来分为两个步骤,从 \(a_i\bmod n\) 位置向后查找,或者如果找不到就从 \(0\) 开始找。
    1.这不就是 upperbound 吗?对于向后查找的部分,我们直接 upperbound 第一个大于 \(a_i\bmod n\) 的位置,如果找到记录并且直接删除,如果找不到(返回了 s.end ()),那么:
    2.我们从 \(0\)\(a_i\bmod n-1\) 查找,也就是 upper_bound 第一个大于 \(-1\) 的位置即可,其余操作同上。

代码:

#include <bits/stdc++.h>
#define f(i ,m ,n ,x) for (int i = (m) ;i <= (n) ;i += (x))
using namespace std ;
template < typename T > inline void read ( T &x ) {
	x = 0 ;
	bool flag (0) ;
	register char ch = getchar () ;
	while (! isdigit (ch)) {
		flag = ch  == '-' ;
		ch = getchar () ;
	}
	while (isdigit (ch)) {
		x = (x << 1) + (x << 3) + (ch ^ 48) ;
		ch = getchar () ;
	}
	flag ? x = -x : 0 ;
} 
const int N = 1e6 + 7 ;
int n ,a[N] ,ans[N] ;  
set < int > s ;
int main () {
	read (n) ;
	f (i ,0 ,n - 1 ,1) {
		read (a[i + 1]) ;
		s.insert (i) ;
	}
	f (i ,1 ,n ,1) {
		if (s.find (a[i] % n) != s.end ()) {
			ans[a[i] % n] = a[i] ;
			s.erase (a[i] % n) ;
		}
		else {
			if (s.upper_bound (a[i] % n) != s.end ()) {
				set < int > :: iterator it = s.upper_bound (a[i] % n) ;
				ans[*it] = a[i] ;
				s.erase (it) ;
				continue ;
			}
			else {
				set < int > :: iterator it = s.upper_bound (-1) ;
				ans[*it] = a[i] ;
				s.erase (it) ;
			}
		}
	}
	f (i ,0 ,n - 1 ,1) {
		cout << ans[i] << " " ; 
	}
	cout << '\n' ;
	return 0 ;
}
posted @ 2024-07-25 14:48  tomzu  阅读(119)  评论(0)    收藏  举报