ACM CSP竞赛笔记(九)——容器与STL库的使用

完整板子可以去资源看,我设置的0积分应该是免费的,如果拿不到可以B站私我。

容器

顺序容器:通过位置来访存元素

容器适配器:集成了基本容器的容器

关联容器:字典,用于表达键值对关系

无序容器:哈希表

Vector

vector初始化长度后,填充不可以用push_back(),否则只是往后加,必须用veci填充。

如果没有初始化长度才可以用push_back().

vector开在main外面。

二维vector

矩形二维vector

可变长一维数组的拼接 与 创建 遍历

这里例子每一行是i的大小递增。

如果要求任意长度,可以用resize(),再push_back进vec4中。

对Vector的遍历

一种是

Resize 操作 例题

带初始值的resize,将行数扩到4,第四行是全0的大小为4的vector

cpp 复制代码
std::vector<std::vector<int>> matrix(3, std::vector<int>(3, 0)); // 初始为 3x3

// 将行数扩容到 4,新增的第4行是一个包含 4 个元素且全为 0 的 vector
matrix.resize(4, std::vector<int>(4, 0)); 

不带初始值的resize,将行数扩到4,第四行是null,在使用的时候必须对列resize。

cpp 复制代码
std::vector<std::vector<int>> matrix(3, std::vector<int>(3, 0)); // 初始为 3x3

// ❌ 危险操作:行数变成了 4,但第 4 行(matrix[3])里面没有分配任何空间!
matrix.resize(4); 

P3613

https://www.luogu.com.cn/problem/P3613

cpp 复制代码
#include<iostream>
#include<vector>
using namespace std;
vector<vector<int>> store;

int main(){
    int n,q;
    cin>>n>>q;
    store.resize(n+1);
    int p,i,j,k;
    for(int v=0;v<q;v++){
        cin>>p;
        if(p==1){
            cin>>i>>j>>k;
            if(store[i].size()<=j){store[i].resize(j+1);}
            store[i][j]=k;
        }else{
            cin>>i>>j;
            cout<<store[i][j]<<endl;
        }
    }
    return 0;
}

List

List的遍历

实际上直接for(auto x:lst)即可。

List的查找

找第一个/找所有的val都可以

可以直接用algorithm的函数

find(begin,end,val)

List的删除

删除单个节点

erase(find(10))

删除所有节点

remove(10)

List的插入

在后面插入 push_back

在前面插入 push_front

在i之前插入

L.insert(i,100)

在i之后插入

auto i=find(5)

i++

L.insert(i,1000)

翻转链表

List与Vector的转换

1. Vector转List

cpp 复制代码
std::vector<int> myVec = {1, 2, 3, 4};
// 利用迭代器范围直接构造 list
std::list<int> myList(myVec.begin(), myVec.end()); 

2. List转Vector

这是最直接的方法。std::vector 提供了一个接受迭代器范围的构造函数,可以直接接收 std::list 的头尾迭代器进行初始化:

cpp 复制代码
std::list<int> myList = {5, 6, 7, 8};
// 利用迭代器范围直接构造 vector
std::vector<int> myVec(myList.begin(), myList.end()); 

应用

快排回去

List排序很慢

cpp 复制代码
std::list<int> myList = {...}; // 假设包含大量无序数据

// 1. 将 list 转换为 vector
std::vector<int> tempVec(myList.begin(), myList.end()); 

// 2. 对 vector 进行高效排序
std::sort(tempVec.begin(), tempVec.end()); 

// 3. 将排好序的数据拷贝回 list
10myList.assign(tempVec.begin(), tempVec.end()); 

队列

入队

头往后

出队

尾巴往前移

环形队列

STL队列 queue

STL双端队列 deque

单调队列(找各区间最大值)

维护一个时间窗口,先检查队头是否过期,如果过期就弹出,然后检查队尾如果比当前小就弹出,然后入队。

优先队列

STL栈

前中后缀表达式

中缀转前缀

中缀转后缀

前后缀表达式的计算

前缀表达式的计算

前缀从右往左压入栈,遇到运算符弹栈计算。

后缀表达式的计算

后缀从左往右压入栈,遇到运算发弹栈计算。注意顺序是次栈顶<运算>栈顶。

练习 P1449

https://www.luogu.com.cn/problem/P1449

cpp 复制代码
#include <iostream>
#include <stack>
#include <string>
using namespace std;
//
int main() {
    string s;
    getline(cin, s);
    stack<long long> st;
    string num = "";
    for (char c : s) {
        //第一个可能是数字也可能是负号,要完整接受一个多位数字:先通过一次性收集齐"."前的字符,然后一次stod变成数字
        if (isdigit(c)) {
            num += c;
        }
        else if (c == '.') {//如果遇到是.就std
            st.push(stoll(num));
            num.clear();//输入完要清空
        }
        else if (c == '@') {
            break;
        }
        else {
            long long op2 = st.top(); st.pop();
            long long op1 = st.top(); st.pop();

            if (c == '+') {
                //cout << op1 <<"+"<< op2 <<"="<<op1+op2 << endl;
                st.push(op1 + op2);

            }
            else if (c == '-') {
                //cout << op1 << "-" << op2 << "=" << op1 - op2 << endl;
                st.push(op1 - op2);

            }
            else if (c == '*') {
                //cout << op1 << "*" << op2 << "=" << op1 * op2 << endl;
                st.push(op1 * op2);

            }
            else {
                //cout << op1 << "/" << op2 << "=" << op1 / op2 << endl;
                st.push(op1 / op2);

            }
        }


    }
    cout << st.top() << endl;
}

单调栈(下一个比自己大的元素 NGE问题)

牛只能看到之前比自己高的牛,比如53214 对于5号牛,只能看到5.对于4号牛能看到532,是一个单调递减栈。每次只需要pop调比自己矮的牛,那么s.size()就是能看到的数量了。

Map 字典(增删改查 且有不重复唯一键 带值去重)

不支持随机访问,必须用it迭代器

会对key建立索引。

如果是自定义类型的key,必须实现<

基本用法

find返回的是迭代器,如果是尾后迭代器就是没找到。

遍历的时候需要用i->first ->second输出元素。

练习

T424396

https://www.luogu.com.cn/problem/T424596

P1918

https://www.luogu.com.cn/problem/P1918

Set(增删改查 且本身就是可作为索引 唯一 去重)

不支持随机访问 必须用it迭代。

注意,set是有序的,是可以建立索引和遍历的。集合是无序的。

注意,set的插入是insert。

set的遍历

输出是有序的 这里插入10 15 11但是输出是唯一且有序的 7 8 10 11 15.

练习

带重复的第k小整数,但是要求不重复的顺序。最核心的痛点就是去重。

注意,set不支持随机访问,必须顺序遍历

cpp 复制代码
//迭代器不可以跳变,只能一个个叠加上去。
    auto it=s.begin();
    for(int i=0;i<k-1;i++){
        it++;
    }
cpp 复制代码
#include<bits/stdc++.h>
using namespace std;

set<int> s;
int main(){
    int n,k;
    cin>>n>>k;
    for(int i=0;i<n;i++){
        int temp;
        cin>>temp;
        s.insert(temp);
    }
    if(k > s.size()){
        cout << "NO RESULT" << endl;
        return 0;
    }
    //迭代器不可以跳变,只能一个个叠加上去。
    auto it=s.begin();
    for(int i=0;i<k-1;i++){
        it++;
    }
    cout<<*it<<endl;
}

set和Map的upper_bound和lower_bound

当查找基于红黑树实现的容器,比如set和map,都可以用bound函数找到第一个满足大于这个值/小于这个值的值。

P5250

https://www.luogu.com.cn/problem/P5250

正确解法:利用 lower_bound

std::set 内部是红黑树,支持 O(log⁡N)的查找。我们可以利用 s.lower_bound(val)

  • 它会返回第一个 大于等于 val 的元素的迭代器。

策略:

  1. 精确查找 :先 find(val),如果有直接取走。
  2. 模糊查找
    • lower_bound(val) 找到第一个≥val 的数,记为 it_high(候选1:偏大的)。
    • 如果 it_high 不是 begin(),那么 it_high 的前一个数 prev(it_high) 一定是 <val 的最大数,记为 it_low(候选2:偏小的)。
    • 比较这两个候选者与 val 的差值。题目要求"一样接近取较短",意味着如果差值相等,优先取 it_low

找第一个大于等于val的迭代器

cpp 复制代码
auto it_high=s.lower_bound(val)

然后就可以用

cpp 复制代码
*(--it_high)

表达后一个

cpp 复制代码
*(++it_high)
cpp 复制代码
#include<bits/stdc++.h>
using namespace std;

set<int> s;
//vector<int> vec;
int main(){
    int m;
    cin>>m;
    for(int i=0;i<m;i++){
        int op=0;
        cin>>op;
        if(op==1){
            int val=0;
            cin>>val;
            auto it=s.find(val);
            if(it==s.end()){//如果没招到
                s.insert(val);
            }else{
                cout<<"Already Exist"<<endl;
            }
        }else if(op==2){
            int val=0;
            cin>>val;
            auto it=s.find(val);
            if(s.empty()){
                cout<<"Empty"<<endl;
            }else if(it!=s.end()){
                s.erase(val);
                cout<<val<<endl;
            }else{//如果找不到长度一样的,就找最小的 直接上二分
                auto it_high=s.lower_bound(val);//去找第一个满足大于val的迭代器
                int chose=0;
                if(it_high==s.end()){//如果所有的都不满足,说明都太短了
                    chose=*(--it_high);
                }else if(it_high==s.begin()){//所有的都大于,都太长了
                    chose=*it_high;
                }else{
                    int v_high=*it_high;
                    int v_low=*(--it_high);

                    if(v_high-val<val-v_low){//更近就选更近的,一样就选短的
                        chose=v_high;
                    }else{
                        chose=v_low;
                    }

                    
                }
                s.erase(chose);
                cout<<chose<<endl;
            }
            
            
        }
    }
}

哈希(超大范围的不重复统计)不咋考

一般都需要offset偏移量

需要将一个大范围压缩到一个小范围时,比如key1的值是-1亿,key4是一亿,就需要映射到小桶中。因为数组不能开1e7的桶。

特性 std::map unordered_map (Hash)
底层结构 红黑树 (平衡二叉树) 哈希表 (数组+链表)
查找/插入速度 logN 1
数据顺序 自动排序 (从小到大) 无序 (乱序)
内存占用 较小 (每个节点只需存左右孩子指针) 很大 (需要预留大量空桶以防冲突)
支持有序访问

Unordered_Map和Unordered_Set

排序sort()

正序

cpp 复制代码
sort(num.begin(), num.end()); 

逆序

cpp 复制代码
// rbegin() 指向最后一个元素,rend() 指向第一个元素的前一个位置
sort(num.rbegin(), num.rend()); 

自定义类型使用sort()

如果 cmp(a, b) 返回 truesort 就认为 a 应该排在 b 的前面。

如果 cmp(a, b) 返回 falsesort 就认为 a 应该排在 b 的后面(或者顺序不变)。

第二关键词排序

cpp 复制代码
// 比较函数:优先按时间升序;时间相同时,按原始编号(idx)升序
bool cmp(const Node& a, const Node& b) {
    if (a.t != b.t) return a.t < b.t;
    return a.idx < b.idx;
}

精度限制输出

cpp 复制代码
cout << fixed << setprecision(10) << ans << endl;

清空/初始化 Memset

对象、多少、大小sizeof

cpp 复制代码
memset(arr, 0, sizeof(arr)); // 将整个数组的每个字节设为0