7.16小学期基础语法记录
📌 C++ initializer_list
1. 概念与作用
✅ 定义
std::initializer_list 是 C++11 引入的一个 轻量容器类模板,用于 接收一个用花括号 {} 包围的值列表。
template <class T>
class initializer_list;
- 常用于构造函数、函数参数的统一初始化。
- 与 C 风格的
T arr[] = {1,2,3};类似,但更加现代化,并且是 类型安全 的。
✅ 典型用途
- 构造对象时批量初始化成员变量
- 向函数传递一组不定数量的参数
- 简化容器初始化(如
std::vector,std::map)
2. 语法与使用
✨ 基本用法
#include <initializer_list>
#include <iostream>
using namespace std;
void printList(initializer_list<int> lst) {
for (auto v : lst) cout << v << " ";
cout << endl;
}
int main() {
initializer_list<int> list1 = {1, 2, 3, 4, 5}; // 声明并初始化
printList({10, 20, 30}); // 直接传递花括号列表
}
输出:
10 20 30
✨ 常见的使用场景
2.1 构造函数重载
#include <initializer_list>
#include <vector>
using namespace std;
class MyClass {
public:
MyClass(initializer_list<int> lst) {
for (auto i : lst) data.push_back(i);
}
void show() {
for (auto i : data) cout << i << " ";
cout << endl;
}
private:
vector<int> data;
};
int main() {
MyClass obj = {1, 2, 3, 4}; // 直接用花括号列表调用
obj.show();
}
2.2 与 STL 容器结合
vector<int> v = {1, 2, 3, 4}; // vector自动支持initializer_list
map<int, string> m = {{1, "a"}, {2, "b"}};
2.3 可变参数函数的简化版
相比于 ... 可变参数,initializer_list 类型安全且更简洁:
int sum(initializer_list<int> lst) {
int s = 0;
for (auto i : lst) s += i;
return s;
}
int main() {
cout << sum({1, 2, 3, 4, 5}); // 输出15
}
3. 底层原理
✅ initializer_list 的数据结构
它并不是一个真正的容器,只是一个 轻量级的包装类,内部保存:
- 一个指向首元素的指针
const T\* - 一个元素数量
size_t
所以它本质上是 只读视图,不允许修改元素。
✅ 常用接口
| 方法 | 作用 |
|---|---|
size() |
返回元素个数 |
begin() |
返回指向首元素的迭代器 |
end() |
返回尾后迭代器 |
示例:
initializer_list<int> lst = {1,2,3};
for (auto it = lst.begin(); it != lst.end(); ++it)
cout << *it << " ";
4. 与其他初始化方式的区别
| 对比对象 | 区别 |
|---|---|
| C数组初始化 | initializer_list 是类型安全的,支持模板推导,而 C 数组需要明确长度 |
变长参数(varargs/...) |
initializer_list 在编译期进行类型检查,更安全 |
| 普通构造函数重载 | 如果有 initializer_list 构造函数,会 优先匹配花括号 {} |
⚠️ 优先级例子:
class Test {
public:
Test(int a, int b) { cout << "普通构造\n"; }
Test(initializer_list<int> l) { cout << "initializer_list 构造\n"; }
};
Test t1(1, 2); // 普通构造
Test t2{1, 2}; // initializer_list 构造 (优先选择)
5. 注意事项与易错点
✅ 1. 只读限制
initializer_list 不允许修改元素:
initializer_list<int> lst = {1,2,3};
// *lst.begin() = 10; ❌ 编译错误
✅ 2. 生命周期问题
initializer_list 只是 指针+长度 的包装,对象生命周期由编译器管理,不要返回其引用。
错误示例:
initializer_list<int> badFunc() {
return {1,2,3}; // ✅ 可以返回副本,但返回引用会悬空
}
✅ 3. 性能注意
由于是浅拷贝(拷贝时只复制指针和大小),性能非常轻量。
✅ 4. 重载冲突
当一个类同时有普通构造函数和 initializer_list 构造函数时,花括号初始化会优先匹配 initializer_list,可能导致意外调用。
6. 高级用法
✨ 1. 模板与泛型
template<typename T>
void print(initializer_list<T> lst) {
for (auto &v : lst) cout << v << " ";
cout << endl;
}
print({1,2,3}); // T自动推导为int
print({"a","b","c"}); // T自动推导为const char*
✨ 2. 嵌套初始化(配合容器)
vector<vector<int>> vv = {
{1, 2, 3},
{4, 5, 6}
};
✨ 3. 自定义类的赋值重载
支持 赋值运算符的initializer_list重载:
class MyVec {
public:
MyVec& operator=(initializer_list<int> lst) {
data.assign(lst.begin(), lst.end());
return *this;
}
private:
vector<int> data;
};
MyVec v;
v = {10,20,30};
7. 总结思维导图(文字版)
initializer_list
├── 定义与作用
│ ├── 统一初始化
│ ├── 类型安全的值列表
├── 常用场景
│ ├── 函数参数
│ ├── 构造函数
│ └── STL容器初始化
├── 原理
│ ├── 内部结构:const T* + size_t
│ └── 只读视图,浅拷贝
├── 特性与区别
│ ├── 优先匹配花括号构造
│ ├── 与变长参数相比更安全
├── 注意事项
│ ├── 不可修改元素
│ ├── 生命周期受限
│ └── 重载优先级问题
└── 高级用法
├── 模板推导
├── 嵌套初始化
└── 赋值运算符重载
好的,下面 补充一节 ——详细解析 initializer_list 在标准库(STL)中的实际应用与实现原理,帮助你从源码层面理解其重要性。
8. initializer_list 在标准库源码中的应用与实现原理
8.1 STL 中的典型应用场景
✅ 1. std::vector 的支持
(1) 直接初始化
std::vector<int> v = {1, 2, 3, 4};
这背后调用的是 vector 的 initializer_list 构造函数:
explicit vector(std::initializer_list<T> ilist,
const Allocator& alloc = Allocator());
其源码逻辑大致如下(简化):
vector(std::initializer_list<T> ilist) {
reserve(ilist.size()); // 预留空间
for (auto& elem : ilist)
push_back(elem); // 逐个拷贝
}
⚠️ 为什么不直接 memcpy?
因为 push_back 保证了元素逐一构造,并能调用 T 的构造函数,而不是简单内存拷贝。
(2) 赋值运算符重载
std::vector<int> v;
v = {5, 6, 7};
背后调用:
vector& operator=(std::initializer_list<T> ilist);
其实现类似于:
vector& operator=(std::initializer_list<T> ilist) {
assign(ilist.begin(), ilist.end());
return *this;
}
✅ 2. std::map / std::unordered_map
直接用花括号初始化键值对:
std::map<int, std::string> m = {{1, "one"}, {2, "two"}};
对应构造函数:
explicit map(std::initializer_list<value_type> il,
const Compare& comp = Compare(),
const Allocator& alloc = Allocator());
源码逻辑:遍历 ilist,调用 insert() 插入键值对。
✅ 3. 其他容器
| 容器 | 典型用法 |
|---|---|
std::set |
std::set<int> s = {1, 2, 3}; |
std::array |
std::array<int,3> arr = {1,2,3}; |
std::string |
std::string s = {'H','i'};(字符列表) |
所有这些都是通过 提供 initializer_list 构造函数重载 实现的。
8.2 源码实现原理分析
✅ 1. 头文件定义
initializer_list 在 <initializer_list> 中定义,简化后的源码如下:
template<class T>
class initializer_list {
private:
const T* _array; // 指向首元素
size_t _size; // 元素数量
// 私有构造函数,编译器会在花括号列表初始化时调用
constexpr initializer_list(const T* a, size_t s)
: _array(a), _size(s) {}
public:
using value_type = T;
using reference = const T&;
using const_reference = const T&;
using size_type = size_t;
using iterator = const T*;
using const_iterator = const T*;
constexpr initializer_list() noexcept : _array(nullptr), _size(0) {}
constexpr size_t size() const noexcept { return _size; }
constexpr const T* begin() const noexcept { return _array; }
constexpr const T* end() const noexcept { return _array + _size; }
};
✅ 2. 编译器的角色
编译器在你写 {1,2,3} 时,自动将其转化为一个临时的 initializer_list 对象。
例如:
vector<int> v = {1,2,3};
大致等价于:
const int __temp[] = {1,2,3};
std::initializer_list<int> __il(__temp, 3);
vector<int> v(__il);
⚠️ 注意:initializer_list 并不拥有这些元素的所有权,只是指针引用,元素通常是编译器生成的临时静态数组。
✅ 3. 生命周期管理
- 临时数组的生命周期 会延长到
initializer_list对象销毁为止。 - 因此 不要保留
initializer_list中元素的指针,它可能指向已经销毁的内存。
错误示例:
const int* p = nullptr;
{
initializer_list<int> lst = {1,2,3};
p = lst.begin();
}
cout << *p; // ❌ 未定义行为
8.3 优先级与陷阱(STL 中常见)
✅ 1. 构造函数重载优先级问题
例如 std::vector:
std::vector<int> v1(10, 1); // 10个元素,每个值1
std::vector<int> v2{10, 1}; // ❗ 被认为是initializer_list,只有两个元素
这是因为:
- 圆括号
()匹配普通构造函数 - 花括号
{}优先匹配initializer_list构造函数
若要避免歧义,显式指定:
std::vector<int> v3({10, 1}); // initializer_list
std::vector<int> v4(10, 1); // 普通构造
✅ 2. 自动类型推导陷阱
auto 推导时,{} 不会直接推导成 initializer_list,而是推导成 std::initializer_list 需要显式声明:
auto a = {1, 2, 3}; // a的类型是initializer_list<int>
但是不能混合类型:
auto b = {1, 2.0}; // ❌ 错误:initializer_list要求类型统一
8.4 总结(源码层面理解)
- 轻量封装:内部只有指针+大小,拷贝成本极低
- 编译器自动生成临时数组:不需要手动管理内存
- 容器普遍提供对应的构造与赋值重载,便于统一初始化
- 花括号优先级高:导致与传统构造函数的重载选择不同
- 生命周期受限:元素多为临时对象,不可长期保存指针
🚲 std::bitset 基础概念
std::bitset 是 C++ 标准库中提供的一种 定长二进制位集容器,它适合用于存储、操作和高效处理一组 二进制标志(bit flags),在算法、位运算优化、状态压缩等场景非常常用。下面我从定义、常用操作、底层原理、高级用法、易错点几个方面详细讲解。
✅ 1. 基本定义
#include <bitset>
std::bitset<N> bs; // 创建一个大小为 N 位的 bitset,每一位初始化为 0
std::bitset<N> bs(val); // val 可以是 unsigned long long,表示用其二进制初始化
std::bitset<N> bs(str); // str 是由 '0' 和 '1' 组成的字符串,从右向左赋值
示例:
std::bitset<8> b1; // 00000000
std::bitset<8> b2(5); // 00000101(二进制形式)
std::bitset<8> b3("10101"); // 00010101
✅ 2. 常用方法与操作
2.1 位访问与修改
| 操作 | 示例 | 说明 |
|---|---|---|
bs[i] |
bs[3] = 1; |
下标从 0 开始,对应二进制的 最低位 |
bs.test(i) |
if(bs.test(3)) |
判断第 i 位是否为 1 |
bs.set(i) |
bs.set(2); |
将第 i 位设置为 1 |
bs.set() |
bs.set(); |
所有位设置为 1 |
bs.reset(i) |
bs.reset(2); |
将第 i 位设置为 0 |
bs.reset() |
bs.reset(); |
所有位清零 |
bs.flip(i) |
bs.flip(2); |
反转第 i 位 |
bs.flip() |
bs.flip(); |
所有位取反 |
示例:
std::bitset<8> bs(5); // 00000101
bs.set(2); // 00000101 -> 00000101 (本来就是1)
bs.flip(0); // 00000100
bs.reset(2); // 00000000
2.2 属性查询
| 方法 | 说明 |
|---|---|
bs.count() |
返回 1 的数量 |
bs.any() |
是否存在 1 |
bs.none() |
是否全为 0 |
bs.all() |
是否全为 1 |
bs.size() |
返回 bitset 的位数 |
示例:
std::bitset<8> bs("101100");
std::cout << bs.count(); // 3
std::cout << bs.any(); // true
std::cout << bs.none(); // false
std::cout << bs.size(); // 8
2.3 数值与字符串转换
| 方法 | 说明 |
|---|---|
bs.to_ulong() |
转为 unsigned long,注意溢出 |
bs.to_ullong() |
转为 unsigned long long,比 to_ulong 安全 |
bs.to_string() |
转为由 '0' 和 '1' 组成的字符串,高位在前 |
示例:
std::bitset<8> bs("101");
std::cout << bs.to_ulong(); // 输出 5
std::cout << bs.to_string(); // 输出 "00000101"
2.4 运算符支持
bitset 支持按位运算(与整数类似):
~bs // 按位取反
bs1 & bs2 // 按位与
bs1 | bs2 // 按位或
bs1 ^ bs2 // 按位异或
bs << n // 左移 n 位
bs >> n // 右移 n 位
示例:
std::bitset<8> a("1010");
std::bitset<8> b("1100");
auto c = a & b; // 1000
std::cout << c; // 00001000
✅ 3. 典型应用场景
(1) 状态压缩/子集枚举
适合处理 n ≤ 64/128 的状态集合:
int n = 5;
for(int i = 0; i < (1 << n); i++){
std::bitset<5> bs(i);
std::cout << bs << '\n';
}
(2) 快速筛法(素数筛/并集运算)
比 vector<bool> 更高效:
const int N = 1e6;
std::bitset<N> isPrime;
isPrime.set(); // 全部置 1
isPrime[0] = isPrime[1] = 0;
for (int i = 2; i * i < N; i++){
if(isPrime[i])
for (int j = i * i; j < N; j += i)
isPrime[j] = 0;
}
(3) 快速集合运算
std::bitset<8> s1("10101010");
std::bitset<8> s2("11001100");
auto inter = s1 & s2; // 交集
auto uni = s1 | s2; // 并集
auto diff = s1 ^ s2; // 对称差
(4) 图算法/DP优化
在 状压DP、传递闭包、快速布尔矩阵乘法中用 bitset 可显著优化,例如:
- 传递闭包 Floyd 的优化:
g[i] |= g[k] & g[i];
✅ 4. 性能与底层原理
- 底层实现通常是定长数组,内部用
unsigned long/unsigned long long做块状存储,比vector<bool>高效且无特殊代理引用。 - 大小在编译期固定,不支持动态调整(要动态大小请用
std::vector<bool>或boost::dynamic_bitset)。 - 位运算在硬件级别并行执行,适合大规模布尔运算。
✅ 5. 易错点与注意事项
-
固定大小,编译期确定
int n; std::cin >> n; std::bitset<n> bs; // ❌ 不允许,n 必须是编译期常量 -
to_ulong溢出
如果 bitset 超过unsigned long能表示的范围,会抛出std::overflow_error,推荐用to_ullong。 -
下标方向
bs[0]是 最低有效位(右侧),容易与字符串直观方向搞混。 -
大规模时内存占用
对于bitset<1e7>,占用约1.25MB,内存分配在栈上可能爆栈,应使用静态/全局变量。
✅ 6. 进阶:自定义函数封装
例:并查集中的快速集合合并
struct BitsetDSU {
std::vector<std::bitset<1000>> sets;
void unite(int a, int b) {
sets[a] |= sets[b]; // 快速并集
}
bool query(int a, int b) {
return (sets[a] & sets[b]).any(); // 判断是否有交集
}
};
好的,Eigh18n,我给你整理一份《图论+DP中 bitset 优化模板合集》,这份笔记尽量全面、详细、可直接套用,适合算法竞赛和工程实践。内容按常见问题场景分类,并且每个模板会包含:适用场景、核心思路、复杂度分析、代码模板、注意事项。
🗺️ 图论 + DP 中 bitset 优化模板合集
1. Floyd 传递闭包优化
适用场景
- 求有向图的传递闭包(可达性矩阵)。
- 求是否存在路径可达性。
- 动态规划中,判断状态间是否存在可转移关系。
核心思路
- Floyd 的本质是:若 i→ki\to k 且 k→jk\to j,则 i→ji\to j。
- 用
bitset表示一行 g[i]g[i]:g[i][j]=1表示 i 可达 j。 - 更新时:
如果 i 能到 k,那么 i 可达集合 += k 的可达集合。
即g[i] |= g[k]。
复杂度
- 原始复杂度:O(n3)O(n^3)
- 优化后:O(n3/64)O(n^3 / 64) (硬件并行加速)
模板代码
#include <bits/stdc++.h>
using namespace std;
const int N = 2000; // 最大节点数
bitset<N> g[N]; // g[i][j] = i 是否可达 j
void floyd_bitset(int n) {
for (int k = 0; k < n; k++) {
for (int i = 0; i < n; i++) {
if (g[i][k]) // 若 i->k 可达
g[i] |= g[k]; // 把 k 的可达集合并入 i
}
}
}
注意事项
- 初始化:若有边 i→ji\to j,则
g[i][j]=1;g[i][i]=1(自环)。 - n 很大时:bitset 在栈上可能爆栈,建议用 全局变量。
2. 布尔矩阵乘法(多步可达性 / 快速幂)
适用场景
- 求“经过 k 步是否可达”。
- 图的传递闭包的快速幂(k 步可达性)。
- 状态转移矩阵的布尔乘法。
核心思路
- 布尔矩阵乘法定义:
C[i][j]=⋁k(A[i][k]∧B[k][j])C[i][j] = \bigvee_{k} (A[i][k] \land B[k][j]) - 通过转置列存储减少随机访问。
模板代码
const int N = 500;
bitset<N> A[N], B_T[N], C[N]; // B_T 是 B 的转置
void boolean_matrix_multiply(int n) {
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if ((A[i] & B_T[j]).any()) // 按位与后判断是否有交集
C[i][j] = 1;
}
}
}
快速幂:k 步可达性
// 矩阵快速幂求 A^k
vector<bitset<N>> mat_pow(vector<bitset<N>> base, long long k){
vector<bitset<N>> res = base;
while(--k){
// res = res * base
vector<bitset<N>> tmp(N);
for(int i=0;i<N;i++){
for(int j=0;j<N;j++){
if((res[i] & base[j]).any())
tmp[i][j] = 1;
}
}
res = tmp;
}
return res;
}
3. 背包型 DP 优化(0-1 背包 / 子集和问题)
适用场景
- 判断能否恰好装满重量 W。
- 求最大可达重量。
- 多次物品选取(需要背包 DP)。
核心思路
dp[i]表示“能否达到重量 i”。- 状态转移:
传统:dp[i+w]=true if dp[i]=true
bitset:dp |= dp << w。
模板代码
const int MAXW = 100000;
bitset<MAXW+1> dp;
vector<int> weight = {1,3,5,7};
void knapsack_bitset(int W){
dp.reset();
dp[0] = 1;
for(int w : weight)
dp |= (dp << w); // 批量更新
cout << (dp[W] ? "YES" : "NO");
}
复杂度
- O(n * W / 64) → 对 W=1e5,n=1e3 也能轻松通过。
4. Hamilton 路径 / 状压 DP 可达性优化
适用场景
- TSP(旅行商问题)仅判定是否存在 Hamilton 路径。
- 大规模 n≈20~25 的可达性问题。
核心思路
- 用
bitset批量转移:
dp[v][mask] = true if dp[u][mask^(1<<v)] && g[u][v]
模板代码
const int N = 20;
bitset<1<<N> dp[N];
bool g[N][N];
void hamilton_bitset(int n){
dp[0][1<<0] = 1; // 起点
for(int mask=1;mask<(1<<n);mask++){
for(int v=0;v<n;v++){
if(!(mask>>v & 1)) continue;
for(int u=0;u<n;u++){
if(u!=v && (mask>>u &1) && g[u][v] && dp[u][mask^(1<<v)])
dp[v][mask] = 1;
}
}
}
}
- 可用
bitset压缩 dp 的第二维(这里略)。
5. 最大团 / 最大独立集搜索优化
适用场景
- 无向图求最大团或最大独立集。
核心思路
- 经典回溯:逐点枚举 + 剪枝。
- 用
bitset存储当前候选集合,快速取交集。
模板代码
const int N = 100;
bitset<N> G[N];
int ans=0, n;
void dfs(bitset<N> cand, int cnt){
if(cand.none()) {
ans = max(ans, cnt);
return;
}
while(cand.any()){
int v = cand._Find_first(); // 找最低位1
cand.reset(v);
dfs(cand & G[v], cnt + 1); // 只保留与v相连的点
}
}
复杂度
- 实测可优化 10~100 倍,适合 n ≤ 100 的图。
6. BFS 层次优化(多源最短路)
适用场景
- 多源最短路(无权图)。
- 多次批量扩展。
核心思路
- 当前层节点 + 邻接表的按位或,代替逐点遍历。
模板代码
const int N = 5000;
vector<bitset<N>> adj;
bitset<N> cur, nxt, vis;
void bfs_bitset(vector<int> sources, int n){
cur.reset(); vis.reset();
for(int s : sources) { cur.set(s); vis.set(s); }
int d=0;
while(cur.any()){
nxt.reset();
for(int i=0;i<n;i++)
if(cur[i]) nxt |= adj[i]; // 批量加入邻居
nxt &= ~vis;
vis |= nxt;
cur = nxt;
d++;
}
}
7. 二分图匹配的匈牙利算法优化
适用场景
- 稠密图下的二分图最大匹配。
核心思路
- 用 bitset 表示每个点的邻居集,增广路搜索时快速找到未访问点。
模板代码简要
const int N = 500;
bitset<N> adj[N], used;
int match[N];
bool dfs(int u){
bitset<N> cand = adj[u] & ~used;
while(cand.any()){
int v = cand._Find_first();
cand.reset(v);
used.set(v);
if(match[v]==-1 || dfs(match[v])){
match[v]=u; return true;
}
}
return false;
}
8. 位运算 DP(子集卷积 / 状态转移)
适用场景
- 集合卷积问题:求 f(S)=Σg(T)h(S\T)。
- 或/与/异或子集卷积:与 SOS DP 配合。
思路简述
- bitset 直接存储 g(T),卷积时用按位运算和批量更新。
(这类模板更复杂,我可以单独详细展开 SOS DP + bitset 版。)
✅ 总结表
| 算法场景 | 关键操作 | 时间复杂度 |
|---|---|---|
| Floyd 传递闭包 | `g[i] | = g[k]` |
| 布尔矩阵乘法 | (A[i] & B_T[j]).any() |
O(n3/64)O(n^3/64) |
| 0-1 背包 | `dp | = dp << w` |
| Hamilton 路径可达 | `dp[v] | = dp[u]<<v` |
| 最大团搜索 | dfs(cand & G[v]) |
剪枝后约 O(1.2n)O(1.2^n) |
| BFS 层次 | `nxt | = adj[i]` |
| 匈牙利算法 | cand = adj[u] & ~used |
O(n2/64)O(n^2/64) |

浙公网安备 33010602011771号