vector - 动态数组
std::vector 是算法竞赛中最常用的序列容器,提供O(1) 随机访问和均摊 O(1) 尾部插入。掌握 C++11 特性,能让你的代码更快更简洁,且在蓝桥杯考场上编译通过。
竞赛中的核心考点
| 操作 | 时间复杂度 | 竞赛要点 |
|---|
| 随机访问 | O(1) | 替代普通数组,支持动态扩容 |
| 尾部 push/pop | 均摊 O(1) | 用作栈 (stack) |
| 中间插入/删除 | O(n) | 避免在循环中频繁使用 |
reserve | O(n) | 预处理避免 reallocation |
lower_bound | O(log n) | 配合 sort 实现二分查找 |
一、初始化与构造
1.1 竞赛常用初始化
#include <bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
signed main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
vector<int> a(n);
vector<int> b(n, -1);
int n; cin >> n;
vector<int> c(n);
for (auto& x : c)
{
cin >> x;
}
int arr[] = {1, 2, 3, 4, 5};
vector<int> d(arr, arr + 5);
vector<int> e = {1, 2, 3, 4, 5};
vector<pair<int, int>> edges = {{1, 2}, {2, 3}, {3, 4}};
vector<vector<int>> g(n);
vector<vector<int>> mat(n, vector<int>(m));
return 0;
}
1.2 预分配空间 - 避免 TLE
vector<int> v;
for (int i = 0; i < 1e6; i++)
{
v.push_back(i);
}
vector<int> v;
v.reserve(1e6);
for (int i = 0; i < 1e6; i++)
{
v.push_back(i);
}
二、emplace_back vs push_back
2.1 性能对比
struct Node
{
int x, y, w;
Node(int _x, int _y, int _w) : x(_x), y(_y), w(_w) {}
};
vector<Node> v;
v.reserve(100000);
for (int i = 0; i < n; i++)
{
v.push_back(Node(i, i+1, i+2));
}
for (int i = 0; i < n; i++)
{
v.emplace_back(i, i+1, i+2);
}
2.2 竞赛建议
- 对于基本类型 (
int, long long):两者无差别
- 对于自定义结构体:使用
emplace_back 减少开销
- 对于
pair 和 tuple:emplace_back 可以直接传参数
vector<pair<int, int>> edges;
edges.emplace_back(u, v);
edges.emplace_back(u, v, w);
三、遍历技巧
3.1 竞赛常用遍历方式
vector<int> a = {1, 2, 3, 4, 5};
for (int i = 0; i < (int)a.size(); i++)
{
std::cout << a[i];
if (i < (int)a.size() - 1)
{
std::cout << " ";
}
else
{
std::cout << "\n";
}
}
for (const auto& x : a)
{
std::cout << x << " ";
}
for (auto& x : a)
{
x *= 2;
}
3.2 倒序遍历
vector<int> a = {1, 2, 3, 4, 5};
for (auto it = a.rbegin(); it != a.rend(); ++it)
{
std::cout << *it << " ";
}
for (int i = (int)a.size() - 1; i >= 0; i--)
{
std::cout << a[i] << " ";
}
四、算法配合 (C++11 写法)
4.1 C++11 排序与去重
vector<int> a = {3, 1, 4, 1, 5, 9, 2, 6};
sort(all(a));
sort(all(a), greater<int>());
sort(all(a), [](int x, int y)
{
return x % 10 < y % 10;
});
sort(all(a));
a.erase(unique(all(a)), a.end());
4.2 C++11 二分查找
vector<int> a = {1, 3, 5, 7, 9};
auto it = lower_bound(all(a), 5);
if (it != a.end() && *it == 5)
{
std::cout << "找到,下标: " << (it - a.begin());
}
it = upper_bound(all(a), 5);
std::cout << *it;
bool exists = binary_search(all(a), 5);
4.3 前缀和
vector<int> a = {1, 2, 3, 4, 5};
int n = (int)a.size();
vector<long long> pref(n + 1, 0);
for (int i = 0; i < n; i++)
{
pref[i + 1] = pref[i] + a[i];
}
int l = 1, r = 3;
long long sum = pref[r + 1] - pref[l];
五、C++11 安全删除法 (O(N) 性能)
5.1 为什么 erase + 循环是 O(N²) 陷阱
vector<int> v = {1, 2, 3, 4, 5};
for (auto it = v.begin(); it != v.end(); )
{
if (*it % 2 == 0)
{
v.erase(it);
}
else
{
++it;
}
}
for (int i = (int)v.size() - 1; i >= 0; i--)
{
if (v[i] % 2 == 0)
{
v.erase(v.begin() + i);
}
}
5.2 C++11 Erase-Remove 惯用法 (O(N) 性能秒杀)
#include <vector>
#include <algorithm>
vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
v.erase(remove_if(all(v), [](int x)
{
return x % 2 == 0;
}), v.end());
v.erase(remove_if(all(v), [](int x)
{
return x < 5;
}), v.end());
v.erase(remove(all(v), 5), v.end());
5.3 多条件删除
vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
v.erase(remove_if(all(v), [](int x)
{
return x % 2 == 0 && x % 3 == 0;
}), v.end());
int L = 3, R = 7;
v.erase(remove_if(all(v), [&](int x)
{
return x >= L && x < R;
}), v.end());
六、vector 性能天坑
6.1 为什么不推荐使用 vector
vector<bool> flags(n);
flags[0] = true;
6.2 正确替代方案
vector<char> flags(n, 0);
flags[i] = 1;
vector<uint8_t> flags2(n);
vector<int> flags3(n);
6.3 性能对比
| 类型 | 访问时间 | 内存 | 适用场景 |
|---|
vector<bool> | 最慢 | 1 bit | ❌ 不推荐 |
vector<char> | 最快 | 1 byte | ✅ 竞赛首选 |
vector<uint8_t> | 快 | 1 byte | ✅ 明确语义 |
vector<int> | 快 | 4 byte | 需要位运算时 |
七、迭代器失效问题
7.1 失效场景
vector<int> v = {1, 2, 3, 4, 5};
auto it = v.begin() + 2;
v.push_back(6);
7.2 安全删除元素
vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
v.erase(remove_if(all(v), [](int x)
{
return x % 2 == 0;
}), v.end());
for (auto it = v.begin(); it != v.end(); )
{
if (*it % 2 == 0)
{
it = v.erase(it);
}
else
{
++it;
}
}
八、竞赛实战技巧
8.1 用 vector 实现栈
vector<int> stk;
stk.push_back(x);
int top = stk.back();
stk.pop_back();
if (stk.empty())
{
...
}
int sz = (int)stk.size();
8.2 邻接表存图
int n, m;
cin >> n >> m;
vector<vector<pair<int, int>>> g(n);
for (int i = 0; i < m; i++)
{
int u, v, w;
cin >> u >> v >> w;
u--; v--;
g[u].emplace_back(v, w);
}
for (auto& p : g[u])
{
int to = p.first;
int w = p.second;
}
8.3 动态规划中的滚动数组
vector<vector<int>> dp(n, vector<int>(m, 0));
vector<int> dp(m), newdp(m);
for (int i = 0; i < n; i++)
{
for (int j = 0; j < m; j++)
{
newdp[j] = ...;
}
swap(dp, newdp);
}
8.4 离散化
vector<int> a = {100, 50, 200, 50, 100, 300};
vector<int> vals = a;
sort(all(vals));
vals.erase(unique(all(vals)), vals.end());
for (auto& x : a)
{
x = lower_bound(all(vals), x) - vals.begin();
}
九、常见错误与注意事项
| 错误 | 说明 | 解决方案 |
|---|
v[i] 越界 | 未检查 i < v.size() | 使用 at() 或先检查 |
| 迭代器失效 | push_back后迭代器失效 | 重新获取迭代器 |
| erase + 循环 | O(N²) 复杂度导致 TLE | 使用 Erase-Remove 惯用法 |
使用 vector<bool> | 常数巨大,无法返回引用 | 使用 vector<char> |
size() - 1 溢出 | 无符号减法溢出 | 使用 (int)v.size() - 1 |
未 reserve 导致 TLE | 频繁扩容 | 预先 reserve |
十、C++11 完整竞赛模板
#include <bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
signed main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin >> n;
vector<int> a(n);
for (auto& x : a)
{
cin >> x;
}
sort(all(a));
a.erase(unique(all(a)), a.end());
int x;
cin >> x;
auto it = lower_bound(all(a), x);
if (it != a.end() && *it == x)
{
}
a.erase(remove_if(all(a), [](int v)
{
return v < 0;
}), a.end());
return 0;
}
总结:C++11 竞赛要点
- 删除元素:只用 Erase-Remove 惯用法 (O(N)),永远不要 erase + 循环
- 布尔数组:只用
vector<char> 或 vector<uint8_t>
- 输入读入:使用
for (auto& x : v) cin >> x
- 排序二分:使用
sort(all(v)) 和 lower_bound(all(v), x)
- 长度计算:使用
(int)v.size() - 1 代替 v.size() - 1
- 宏定义:使用
#define all(x) x.begin(), x.end() 提高打字速度
- 原地构造:对于自定义类型,使用
emplace_back 减少开销