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 类别

选代器的类别:

  • 输入选代器
  • 输出选代器
  • 前向迭代器
    只能使用 ++ 访问其他元素,使用 ==!= 进行比较。
  • 双向迭代器
    listmapset 等容器提供的迭代器,使用 ++--(但不能是 +-)访问其他元素,使用 ==!= 进行比较。
  • 随机访问选代器
    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 1N2×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 1AiN)以及 Q Q Q 1 ≤ Q ≤ 2 × 1 0 5 1 ≤ Q ≤ 2×10^5 1Q2×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⁵ 1N,Q2×105 0 ≤ A i ≤ 1 0 9 0 ≤ Aᵢ ≤ 10⁹ 0Ai109)。
k k k 个查询的格式如下: i k   x k iₖ\ xₖ ik xk 1 ≤ i k ≤ N 1 ≤ iₖ ≤ N 1ikN 0 ≤ x k ≤ 1 0 9 0 ≤ xₖ ≤ 10⁹ 0xk109)。
处理每个查询时,首先将 A A A 中第 i k iₖ ik 个数更新为 x k xₖ xk(此更新对之后的所有查询均有效),然后输出序列 A A Amex 值。
这里的 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,,L1 处)标记了一条线作为标记。
现给出 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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值