CSP-J刷题训练 1
一、基础知识
1. vector
1.1 概念
1、动态大小
2、随机访问
3、插入/删除操作
1.2 初始化
vector<int>a={1,2,3};//初始化为{1,2,3}
vector<char>b(10,'a');//将10格元素全部初始化为'a'
vector<int>c(5);//初始化5个0
vector<int>d(a);//同类型拷贝初始化
double s[5]={1.1,2.2,3.3,4.4,5.5};
vector<double>e(s+1,s+4);//数组地址初始化为{2.2,3.3,4.4},s+4指向哨兵位置
1.3 功能函数
方法 | 功能 |
---|---|
vec.push_back(x) | 在尾部增加一个元素 x x x |
vec.front() | 返回首元素的引用 |
vec.back() | 返回尾元素的引用 |
vec.size() | 返回元素的个数 |
vec.max_size() | 返回最大可允许的元素数量值 |
vec.resize(n) | 改变 vec 的实际大小为
n
n
n |
vec.reserve(n) | 预留 n n n 个元素的空间 |
vec.at(pos) | 返回 p o s pos pos 位置素的引用 |
vec.empty() | 判断是否为空 |
vec.pop_back() | 删除最后一个元素 |
vec.clear() | 清空所有元素 |
vec.begin() | 返回头指针,指向第一个元素 |
vec.end() | 返回尾指针,指向最后一个元素的下一个位置 |
vec.erase(it) | 删除选代器指向元素 |
2. 迭代器
2.1 概念
选代器的作用与指针类似,可通过引用操作(*
)访问其所指向的元素内容。 常用的容器都可以使用一对迭代器来表示范围,即 begin
(指向该容器
第一个元素)和 end
(指向该容器的哨兵位置)。
2.2 类别
选代器的类别:
- 输入选代器
- 输出选代器
- 前向迭代器
只能使用++
访问其他元素,使用==
和!=
进行比较。 - 双向迭代器
list
,map
,set
等容器提供的迭代器,使用++
或--
(但不能是+
或-
)访问其他元素,使用==
和!=
进行比较。 - 随机访问选代器
vector
提供的迭代器,使用++
、--
、+=
、-=
访问其他元素,使用==
、!=
、<
、<=
、>
、>=
进行比较。可以使用[]
或反复读写元素(除非元素是const
的)。在+=
、-=
、+
、-
后写上一整数,表示随机访问的元素与当前迭代器的距离。
2.3 vector 相关功能函数
方法 | 功能 |
---|---|
vec.begin() | 返回头指针,指向第一个元素 |
vec.end() | 返回尾指针,指向最后一个元素的下一个位置 |
vec.erase(it) | 删除送代器指向元素 |
vec.erase(first,last) | 删除 [first,last) 中元素 |
vec.insert(it,x) | 送代器指向元素前增加一个元素 |
vec.insert(it,n,x) | 送代器指向元素前增加 n n n 个相同的元素 |
vec.insert(it,first,last) | 送代器指向元素前插入另一个相同类型向量的 [first,last) 间的数据 |
vec.rbegin() | 反向迭代器,指向最后一个元素 |
vec.rend() | 反向送代器,指向第一个元素之前的位置 |
reverse(first,last) | 翻转 [first,last) 中元素 |
2.4 定义
vector<int>::iterator it;//直接定义
auto it=vec.begin();//不想背用auto
3. 二分函数
3.1 基础函数
返回 vec
中第一个值
≥
\ge
≥ val
的元素下标。
lower_bound(vec.begin(),vec.end(),val)-vec.begin();
返回 vec
中第一个值
>
>
> val
的元素下标。
upper_bound(vec.begin(),vec.end(),val)-vec.begin();
3.2 拓展函数
询问递增序列中值位于闭区间 [ x , y ] [x,y] [x,y] 中的元素个数:
upper_bound(vec.begin(),vec.end(),y)-lower_bound(vec.begin(),vec.end(), x)
询问递增序列中值位于开区间 ( x , y ) (x,y) (x,y) 中的元素个数:
lower_bound(vec.begin(),vec.end(),y)-upper_bound(vec.begin(),vec.end(), x)
4. set
4.1 概念
STL
提供的一个关联容器,一般称其为集合。用于存储一组唯一、自动排序的元素。它基于红黑树实现,提供高效的插入、删除和查找操作,时间
复杂度均为
O
(
log
n
)
O(\log n)
O(logn)。set
默认按升序排序,支持自定义排序规则,常用于需要去重且有序的数据存储场景。
4.2 定义
#include<set>
set<int>st;
4.3 功能函数
方法 | 功能 |
---|---|
st.insert(x) | 向集合中增加一个元素 x x x |
st.erase(x/it/itl,itr) | 删除集合中的元素
x
x
x 或迭代器
i
i
i 指向的元素 [itl,itr) 中的元素 |
st.count(x) | 返回 x x x 在集合中出现的次数(非 1 1 1 即 0 0 0) |
st.find(x) | 返回集合中指向元素
x
x
x 的选代器,失败返回 st.end() |
st.lower_bound(x) | 返回第一个大于等于
x
x
x 的元素的选代器,失败返回 st.end() ,比直接调用算法库的二分函数快 |
st.upper_bound(x) | 返回第一个大于
x
x
x 的元素的选代器,失败返回 st.end() ,比直接条用算法库的二分函数快 |
st.begin() | 返回集合头部元素(最小值)的选代器 |
st.end() | 返回集合尾部下一个空元素的选代器 |
st.size() | 返回集合中元素的个数 |
st.clear() | 清空集合所有元素 |
二、典型例题
1. 区间 X 的个数
1.1 审题
给定一个长度为 N N N( 1 ≤ N ≤ 2 × 1 0 5 1 ≤ N ≤ 2×10^5 1≤N≤2×105)的序列 A = ( A 1 , A 2 , ⋯ , A N ) A = (A_1, A_2, \cdots, A_N) A=(A1,A2,⋯,AN)( 1 ≤ A i ≤ N 1 ≤ A_i ≤ N 1≤Ai≤N)以及 Q Q Q( 1 ≤ Q ≤ 2 × 1 0 5 1 ≤ Q ≤ 2×10^5 1≤Q≤2×105)组查询,每组查询包含三个整数 L , R , X L,R,X L,R,X。要求对于每组查询,统计在子序列 [ A L , A R ] [A_L,A_R] [AL,AR] 中数值等于 X X X 的元素个数。
1.2 思路
我们可以将每个数出现的下标存储一遍。例如 a = { 3 , 1 , 4 , 1 , 5 } a=\{3,1,4,1,5\} a={3,1,4,1,5} 时,存储: a 1 = { 2 , 4 } , a 2 = { } , a 3 = { 1 } , a 4 = { 3 } , a 5 = { 5 } a_1=\{2,4\},a_2=\{\},a_3=\{1\},a_4=\{3\},a_5=\{5\} a1={2,4},a2={},a3={1},a4={3},a5={5},表示 1 1 1 出现在了下标为 2 , 4 2,4 2,4 的位置,以此类推。在询问的时候,只要取出区间内的长度即可。
1.3 参考答案
#include<bits/stdc++.h>
using namespace std;
const int MAXN=2e5+8;
int n,q;
vector<int>idx[MAXN];//idx[i]:i出现的下标
int main(){
cin>>n;
for(int i=1,t;i<=n;i++)
cin>>t,idx[t].push_back(i);
cin>>q;
while(q--){
int l,r,x;
cin>>l>>r>>x;
cout<<upper_bound(idx[x].begin(),idx[x].end(),r)-lower_bound(idx[x].begin(),idx[x].end(),l)<<endl;
}
return 0;
}
2. MEX 的更新
2.1 审题
给定一个长度为
N
N
N 的序列
A
=
(
A
1
,
A
2
,
⋯
,
A
N
)
A = (A₁, A₂, \cdots, A_N)
A=(A1,A2,⋯,AN)。请按给定顺序处理接下来的
Q
Q
Q 个查询(
1
≤
N
,
Q
≤
2
×
1
0
5
1 ≤ N, Q ≤ 2 × 10⁵
1≤N,Q≤2×105,
0
≤
A
i
≤
1
0
9
0 ≤ Aᵢ ≤ 10⁹
0≤Ai≤109)。
第
k
k
k 个查询的格式如下:
i
k
x
k
iₖ\ xₖ
ik xk(
1
≤
i
k
≤
N
1 ≤ iₖ ≤ N
1≤ik≤N,
0
≤
x
k
≤
1
0
9
0 ≤ xₖ ≤ 10⁹
0≤xk≤109)。
处理每个查询时,首先将
A
A
A 中第
i
k
iₖ
ik 个数更新为
x
k
xₖ
xk(此更新对之后的所有查询均有效),然后输出序列
A
A
A 的 mex
值。
这里的 mex
值指的是序列
A
A
A 中没有出现的最小非负整数。
2.2 分析
整体思路是通过更新序列和维护未出现数字的集合来实现对 mex
值的查询和输出。
首先读取序列长度
n
n
n 和查询次数
q
q
q,然后读入长度为
n
n
n 的序列
a
a
a,并统计各个数字出现的次数存储在 cnt
数组中。接着找出未出现过的数字并存储在集合 st
中。接下来,依次处理每一个查询,首先更新序列中指定索引的数字为给定值,然后更新未出现数字集合 st
和数字出现次数 cnt
。最后输出每次更新后未出现的最小非负整数。
2.3 参考答案
#include<bits/stdc++.h>
using namespace std;
const int MAXN=2e5+8;
int n,q,a[MAXN],cnt[MAXN];
set<int>st;//存储未出现过的数字
int main(){
cin>>n>>q;
for(int i=1;i<=n;i++){
cin>>a[i];
if(a[i]<0||a[i]>n)a[i]=n+1;//将不合法的数字更新为n+1
cnt[a[i]]++;
}
//如果某个数字没有出现过,将其加入到集合st中
for(int i=0;i<=n+1;i++)
if(cnt[i]==0)
st.insert(i);
for(int i=1,idx,x;i<=q;i++){
cin>>idx>>x;
if(x<0||x>n)x=n+1;//将不合法的数字更新为n+1
cnt[a[idx]]--;//更新对应数字的出现次数
if(cnt[a[idx]]==0)st.insert(a[idx]);//如果更新后的数字不再出现,将其加入到集合st中
a[idx]=x;//更新索引对应的数字
if(cnt[a[idx]]==0)st.erase(a[idx]);//如果更新后的数字不再缺失,从集合st中移除
cnt[a[idx]]++;//更新新数字的出现次数
cout<<*st.begin()<<endl;//set默认升序排序,最小的就是首位元素
}
return 0;
}
3. 切断木材
3.1 审题
有一根长
L
L
L 米的直线状木材。在木材上,从左端开始,每隔
1
1
1 米(即在位置
x
=
1
,
2
,
⋯
,
L
−
1
x = 1, 2, \cdots, L−1
x=1,2,⋯,L−1 处)标记了一条线作为标记。
现给出
Q
Q
Q 个查询,第
i
i
i 个查询用一对数字
(
c
i
,
x
i
)
(c_i, x_i)
(ci,xi) 表示,请按照查询顺序依次处理。
处理查询的规则如下:
- 当 c i = 1 c_i = 1 ci=1 时:在位置 x i x_i xi 处将木材切断,切成两段。
- 当 c i = 2 c_i = 2 ci=2 时:选择包含标记 x i x_i xi 的那段木材,输出该段木材的长度。
题目保证,对于任意查询,不论 c i c_i ci 是 1 1 1 还是 2 2 2,在该查询执行时位置 x i x_i xi 的标记尚未被切断。
3.2 分析
这是一道典型的迭代器的题目,其中 c i = 2 c_i=2 ci=2 时要熟练运用二分函数来找到上一个和下一个切的位置,进行减法,即当前段的长度。
3.3 参考答案
#include<bits/stdc++.h>
using namespace std;
int l,q;
set<int>st;//存储木材上各个位置的标记
int main(){
cin>>l>>q;
st.insert(0),st.insert(l);
for(int i=1,c,x;i<=q;i++){
cin>>c>>x;
if(c==1)st.insert(x);
else{
//itr:找到第一个>=x的位置的迭代器(下一个切的位置)
auto itr=st.lower_bound(x);//set<int>::iterator itr=st.lower_bound(x);
auto itl=itr;//复制迭代器
itl--;//找到<x的位置的迭代器(上一个切的位置)
cout<<*itr-*itl<<endl;//输出包含标记x的木材段的长度
}
}
return 0;
}