【基础】有用的C++新特性和新STL
Reduce (C++17)
ll s = reduce(a + 1, a + 1 + n); // 求和
ll s = reduce(a + 1, a + 1 + n, std::plus<ll>); // 求和
double s = reduce(a + 1, a + 1 + n, std::multiples<double>); // 求积
int s = reduce(a + 1, a + 1 + n, std::max<int>); // 求最大值
把一个数组压缩成一个数字的办法,不保证他们内部的分组和运行的方法。需要传入一个有结合律(associative property)和交换律(commutative property)的二元运算符(注意浮点数的加法是有误差的)。
由于不需要多余的操作,比较好写。
另外这里也暗示了线段树可以通过传入一个二元运算符来实现,TODO。
https://en.cppreference.com/w/cpp/algorithm/reduce
Accumulate
对比常见的 Accumulate,需要传入一个初始值,和一个无需条件的二元运算符,他一定会从左到右执行:
std::vector<int> v{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int sum = std::accumulate(v.begin(), v.end(), 0);
int product = std::accumulate(v.begin(), v.end(), 1, std::multiplies<int>());
但是这个要放一个初始值,初始值是必传的参数,不如上面那个reduce好用。而且reduce跑得还更快。限制是reduce是要求满足交换律和结合律的。
minmax
auto [m, M] = std::minmax(a, b); // 把a和b之间的最小值放到m,最大值放到M
// 其实不如:
if (a > b) swap (a, b); // 非常直观
auto [m, M] = std::minmax(vector_name); // 这个就比较好用,可以放vector或者任何可以迭代遍历的东西进去,不需要用minmax_elements这么复杂。
缺点是要用从0开始的C++ style的数组(vector),感觉可以先留着,等做一个C++ style和C style的模板库的时候可以用这个性质去优化一下。具体来说就是C style的从1开始的静态数组,通过指针来传值,C++ style的换成用vector或者string,至于从哪个节点开始是取决于问题的类型的,C++style的后缀自动机和Treap之类的,应该就会有node[i]的概念,比如node[i].cnt, node[i].link,而不是C style的cnt[i]和link[i]。其实说不定是哪一种更好,因为C++ style是可以用auto ni = node[i],然后变成ni.link其实也不是很长。另外在处理多层嵌套的时候,会比较符合人类的思维方式。
minmax_elements
相当于是min_element和max_element的结合体,返回一个pair,里面是俩迭代器,一次找到最小值和最大值的位置。
set和map
虽然对大部分实现都很熟悉了,但是set和map是可以在for迭代中删除或者添加新的元素的,insert和erase都会返回一个迭代器,可以接着用!!!这个可以好好练习一下怎么在遍历中删除。
C++新特性
C++11
右值引用和move语义 返回函数内生成的临时对象,如vector,用于避免OIer最喜欢使用的全局变量的问题,另外改成局部变量也会有更好的局部性而显著改善运行速度。而且CF不存在爆栈的问题。放心使用。
constexpr 用来指示函数的返回值是一个编译期的常量,在声明线段树的size中,如果线段树的最大叶子节点数量MAXN确定,那么可以用constexpr计算出线段树的节点数量,避免额外的空间开销。不再需要粗暴地变成4倍容量了,只需要是比MAXN大的二的幂次的2倍。
初始化列表 从C++11开始,vector等“存在顺序元素”的容器可以使用初始化列表进行赋值了,比如可以:
vector<int> vec = {1, 2, 3, 4, 5}
或者在返回值为vector类型中直接返回 {-1, -1}
注意,vector的构造函数的参数使用的是圆括号,而初始化列表使用的是花括号,这是没有歧义的。
auto关键字
引用类型必须声明为 auto& 常见于需要修改vector等容器的内容时
下列的声明是合法的:
auto [m, M] = minmax(a, a + n)
一次声明两个变量m和M,其分别对应minmax返回的pair的第一个和第二个元素,自动解包然后具有名字和正确的类型。这使得priority_queue实现的Dijkstra可以进一步简化。
decltype(变量名) 返回一个与这个变量名相同类型的类型,比auto更方便程序员阅读,让你知道这个变量的类型是一定跟上一个变量是一致的。auto只有编译器才能知道,有时候可能会藏一些bug,也不容易做自动推导。这个方法在给线段树、Treap、01Trie的模板使用可以分离控制树形的变量和实际存储数据的变量。有时间要推广到所有的板子上。
范围for
for (auto &x : vec)
相当于
for (auto it = vec.begin(); it != vec.end(); ++it)
auto x = *it;
或者
for (int i = 0; i < vec.size(); ++i)
auto x = vec[i];
另外就是函数的返回类型可以定义成auto了,这样就避免了很多改来改去忘记类型的情况。也可以 auto f(int a, int b) -> int { 这样定义函数
lambda表达式
只推荐引用捕获的形式[&],可以把任何里面使用到的变量全部引用捕获进去,不需要额外的参数,使得原本局部变量要传入一大堆参数让人头大以致于想用全局变量的问题得到解决。
与之对应的有[=]全部值捕获, [&, x]:x用值捕获,其他的是引用捕获,[=, &x]:x是引用捕获,其他的是值捕获。[]不捕获,不使用外部的变量。
下面是一种典型的树形dfs的递归的写法。需要注意的是,如果要用到dfs的返回值,则需要显式声明返回值的类型 & -> return_type {}
auto dfs = [&](auto self, int u, int p) {
for (int &v: G[u]) {
if (v == p) {
continue;
}
self (self, v, u);
}
};
但是这种写法作为成员函数使用时有点点奇怪,算了在算法竞赛里面应该没有人会这样用。这个lambda表达式最有用的地方应该是作为sort的cmp函数使用(使得不需要暴露于全局),或者是作为二分的check函数使用(使得不需要暴露于全局,也不需要额外的局部变量传参)。另外还有一系列外部不需要理解的接口也应该可以这样使用。
组合数学中的init函数,这个真的没必要,在init中记录一个inited也足够了。另外欧拉筛也是可以自动初始化(就是在sieve中记录一个静态的inited变量即可)
unordered 系列
reserver 3-5倍容量
clog取代cerr
柯南哥在for-each遍历set和map的过程中是可以插入删除的,要学会这俩是如何返回他们的迭代器的
numeric系列,有gcd和lcm了
线段树二分不会写
欧拉路和哈密尔顿回路的模板缺失