[pre] STL 容器的分类:
标准STL序列容器:vector、string、deque和list。
标准STL关联容器:set、multiset、map和multimap。
非标准序列容器slist和rope。slist是一个单向链表,rope本质上是一“重型”string。
非标准的关联容器hash_set、hase_multiset、hash_map和hash_multimap。
(一)vector
vector 的好处就是它分配的是动态空间。
vector <int> v;
12_23
不vector 的好处远不止上述那样简单!
vector
类是以容器(Container) 模式为基准设计的,也就是说,基本上它有 begin()
,end()
,size()
,max_size()
,empty()
以及 swap()
这几个方法。
from wiki:
- 访问元素的方法
vec[i]
- 访问索引值为 i 的元素引用。 (索引值从零起算,故第一个元素是vec[0]。)vec.at(i)
- 访问索引值为 i 的元素的引用,以 at() 访问会做数组边界检查,如果访问越界将会抛出一个例外,这是与operator[]的唯一差异。vec.front()
- 回传 vector 第一个元素的引用。vec.back()
- 回传 vector 最尾元素的引用。
- 新增或移除元素的方法
vec.push_back()
- 新增元素至 vector 的尾端,必要时会进行存储器配置。vec.pop_back()
- 删除 vector 最尾端的元素。vec.insert()
- 插入一个或多个元素至 vector 内的任意位置。vec.erase()
- 删除 vector 中一个或多个元素。vec.clear()
- 清空所有元素。
- 获取长度/容量
vec.size()
- 获取 vector 目前持有的元素个数。vec.empty()
- 如果 vector 内部为空,则传回 true 值。vec.capacity()
- 获取 vector 目前可容纳的最大元素个数。这个方法与存储器的配置有关,它通常只会增加,不会因为元素被删减而随之减少。
- 重新配置/重置长度
vec.reserve()
- 如有必要,可改变 vector 的容量大小(配置更多的存储器)。在众多的 STL 实做,容量只能增加,不可以减少。vec.resize()
- 改变 vector 目前持有的元素个数。
- 迭代 (Iterator)
vec.begin()
- 回传一个Iterator,它指向 vector 第一个元素。vec.end()
- 回传一个Iterator,它指向 vector 最尾端元素的下一个位置(请注意:它不是最末元素)。vec.rbegin()
- 回传一个反向Iterator,它指向 vector 最尾端元素的。vec.rend()
- 回传一个Iterator,它指向 vector 的第一个元素。
vec.insert()
- 插入一个或多个元素至 vector 内的任意位置。这一行了吗? 它支持像rope一样在特定位置插入一个或多个元素。
于是就可以用它来写平衡树的题了?
函数upper_bound()返回的在前闭后开区间查找的关键字的上界
bzoj 3224
用十几行水过不能再帅!
#include <iostream>
#include <cstdio>
#include <vector>
using namespace std;
vector <int> a;
int main()
{
int n; scanf("%d", &n);
for(int i = 1; i <= n; i ++){
int la, x; scanf("%d%d", &la, &x);
if(la == 1)a.insert(upper_bound(a.begin(), a.end(), x), x);
if(la == 2)a.erase(lower_bound(a.begin(), a.end(), x));
if(la == 3)printf("%d\n", lower_bound(a.begin(), a.end(), x) - a.begin() + 1);
if(la == 4)printf("%d\n", a[x - 1]);
if(la == 5)printf("%d\n", *-- lower_bound(a.begin(), a.end(), x));
if(la == 6)printf("%d\n", *upper_bound(a.begin(), a.end(), x));
}
return 0;
}
常用函数总结:
upper_bound(a.begin(), a.end(), x);
lower_bound(a.begin(), a.end(), x);
a.insert(pos, x);
a.erase(pos);
a.push_back(x)
(二) set
set 做的事情是动态地维护一个排序, 支持 查找键值 为 K 的点和 删除 简直 为K 的点 (但不能 超找或删除第K大的点)(而且它所包含的元素的值是唯一的)
set<int> s1;
set<char, scmp> s2; 排序准则自定义为 scmp
begin() ,返回set容器的第一个元素
end() ,返回set容器的最后一个元素
clear() ,删除set容器中的所有的元素
empty() ,判断set容器是否为空
max_size() ,返回set容器可能包含的元素最大个数
size() ,返回当前set容器中的元素个数
erase(iterator) ,删除定位器iterator指向的值
erase(first,second),删除定位器first和second之间的值
erase(key_value),删除键值key_value的值
find() ,返回给定值值得定位器,如果没找到则返回end()。
insert(key_value); 将key_value插入到set中 ,返回值是pair<set<int>::iterator,bool>,bool标志着插入是否成功,而iterator代表插入的位置,若key_value已经在set中,则iterator表示的key_value在set中的位置。
inset(first,second);将定位器first到second之间的元素插入到set中,返回值是void.
lower_bound(key_value) ,返回第一个大于等于key_value的定位器
upper_bound(key_value),返回最后一个大于等于key_value的定位器
例:
bzoj 1208 宠物收养所
当时用splay 打怎么也得六七十行呢吧。
用set 一下子变成普及组难度。。
User | Problem | Result | Memory | Time | Language | Code_Length | Submit_Time |
set版 | 1208 | Accepted | 1408 kb | 264 ms | C++/Edit | 779 B | 2014-12-15 20:36:07 |
splay版 | 1208 | Accepted | 2524 kb | 240 ms | C++/Edit | 2013 B | 2014-12-02 14:30:44 |
对比代码长度, 占用内存, 和时间, 觉得 set 的优势真的挺大的。
#include <iostream>
#include <cstdio>
#include <set>
#define INF 1<<31 - 1
#define mod 1000000
using namespace std;
int n, flag = -1, ans;
set <int> d;
int main()
{
scanf("%d", &n);
d.insert(-INF); d.insert(INF);
for(int i = 1; i <= n; i ++){
int a, b; scanf("%d%d", &a, &b);
if(a == flag || d.size() == 2)flag = a, d.insert(b);
else{
int r = *d.lower_bound(b), l = *-- d.lower_bound(b);
if(b - l <= r - b && l != -INF){ans += b - l; d.erase(l);}
else {ans += r - b; d.erase(r);}
ans %= mod;
}
} cout<<ans<<endl;
return 0;
}
(三), map
map 真的很常用, 学了不下五遍了。
map 就是一个一对一 的数据映射, 它存的是一个pair,
例:
hdu 1004
虽然这题本来就不难, 但是觉得这种STL题简直是在秀下线。
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <map>
#define si string, int
#define smap map <si>
using namespace std;
smap s;
inline bool cmp(pair<si> a, pair<si> b){return a.second < b.second;}
int main()
{
int n; while(scanf("%d", &n) && n){
s.clear(); char ch[22];
for(int i = 1; i <= n; i ++)scanf("%s", ch), s[ch] ++;
smap::iterator la = max_element(s.begin(), s.end(), cmp);
cout<<la -> first<<endl;
}
return 0;
}
(四) , rope
Operation | Rope | String |
---|---|---|
Index[1] | O(log n) | O(1) |
Split[1] | O(log n) | O(1) |
Concatenate (destructive) | O(log n) | O(n) |
Concatenate (nondestructive) | O(n) | O(n) |
Iterate over each character[1] | O(n) | O(n) |
Insert | O(log n) | O(n) |
Append | O(log n) | O(1) amortized, O(n) worst case |
Delete | O(log n) | O(n) |
Report | O(j + log n) | O(j) |
Build | O(n) | O(n) |
上面的表格已经可以很好的说明rope和string的差别了。
rope的特点就是维护一个串, 支持log n插入和删除制定位置的字符。
bzoj 1507
splay写这题的时候。。本机测可过bzoj莫名RE
rope写得被吓傻。。
用rope的时候,注意要多加的是:
#include <ext/rope>
using namespace __gnu_cxx (这行要记得(虽然它是下划线开头的, 但是是声明空间所以可以用))。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <ext/rope>
using namespace std;
using namespace __gnu_cxx;
char s[2500005], s1[22];
int n, now, k;
crope S;
int main()
{
scanf("%d", &n);
while(n --){scanf("%s", s1);
if(s1[0] == 'M')scanf("%d", &now);
if(s1[0] == 'P')now --;
if(s1[0] == 'N')now ++;
if(s1[0] == 'I'){
scanf("%d", &k); getchar();
for(int i = 0; i < k; i ++)do{s[i] = getchar();}while(s[i] == '\n');
s[k] = 0;
S.insert(now, s);
}
if(s1[0] == 'D')scanf("%d", &k), S.erase(now, k);
if(s1[0] == 'G')scanf("%d", &k), S.copy(now, k, s), s[k] = 0, puts(s);
}
return 0;
}
bzoj 1269
/************转自hzwer*********************/
由于rope的底层实现,insert,erase,get都是logn的
就是翻转不行,不是自己手写的打不了标记啊!!
怎么办?
答:同时维护一正一反两个rope……反转即交换两个子串……Orz……
区间循环位移?简单,拆成多个子串连起来就好了……
区间a变b b变c c变d …… z变a? 呃……维护26个rope?
区间和?滚蛋,那是线段树的活
区间kth?sorry,与数值有关的操作rope一概不支持……
函数 | 功能 |
push_back(x) | 在末尾添加x |
insert(pos,x) | 在pos插入x |
erase(pos,x) | 从pos开始删除x个 |
replace(pos,x) | 从pos开始换成x |
substr(pos,x) | 提取pos开始x个 |
at(x)/[x] | 访问第x个元素 |
/********************************************/