0%

C++ - SWPUACM Wiki

算法竞赛中的 C++ 语法操作

前言

昨天在某群里看到有人问 「有没有那种总结好的语法技巧啊」。突然想到自己也没有做过类似的总结,于是就有了这篇文章。需要注意的是,本文仅列出了一些常见技巧,欢迎指正以及补充

本文的写作顺序_并不总_符合认知的先后,如果有任何疑问,请善用搜索引擎或 Ctrl-F 查找该内容。

算法竞赛代码与工程代码目的与标准并不完全相同,请勿以工程角度看待此文章中的若干「技巧」。同时笔者也需要指出,许多算法竞赛的习惯并不适用于工程。

  • ! 标记了最近新增的条目。
  • * 标记了最近修改的条目。
  • ~ 标记了「这其实是一个_坏_习惯,但算法竞赛就这样用吧」。
  • ^ 标记了准备删除的条目,笔者将在下次更新中移除这些条目。

作者是个老鸽子,乐观情况下_平均_一个月维护一次。另,本文有搬迁至 github 的想法,届时文章将会更加精美全面,敬请期待。

(2023-04-10)假的,可能等不到了,现附上 Markdown,读者可随意使用,欢迎接力。

预处理 1

1

cppreference 预处理器 https://en.cppreference.com/w/cpp/preprocessor

~ 万能头 2 及其预编译 —— #include

2

为什么你不应该使用万能头 https://stackoverflow.com/questions/31816095/why-should-i-not-include-bits-stdc-h

要使用万能头,请使用 g++ 作为编译器。

1
#include <bits/stdc++.h>

另,万能头本身十分庞大,预编译是节省编译时间的重要手段,具体方法如下:

  1. 找到本机 bits/stdc++.h ,笔者该文件位置在 /usr/include/c++/12.1.1/x86_64-pc-linux-gnu/bits/stdc++.h
  2. 运行 g++ --yourflags bits/stdc++.h ,其中 --yourflags 代表你喜好的参数
  3. 下次编译也使用第 2 步提到的指令编译。为节省生命,建议写入 CMakeLists.txt

文本替换宏 3 —— #define / #undef / 预定义替换宏

3

https://en.cppreference.com/w/cpp/preprocessor/replace

许多选手喜欢定义若干宏,读者要决定是否喜欢这些大可自行尝试。在此笔者列出一些较为常见的文本替换宏:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#define int long long   // 可能报告为错误(事实上确实不应这样做)
// 下文或 #undef int 之后 int main() { }
// 或 signed / int32_t main() { }

// ##x 将连接 x,如 i##i 传入 x2 最终会变为 x2x2
#define rep(i, s, e) for (auto i = s, i##i = e; i <= i##i; i ++)
#define per(i, s, e) for (auto i = s, i##i = e; i >= i##i; i --)

#define ls i << 1 // 二叉树的数组实现,ls 表示左儿子 2i
#define rs ls | 1 // 同上,rs 表示右儿子 2i + 1

// 小技巧:想选中一段区间可使用 sort(3 + all(x) - 4)
#define all(x) (x).begin(), (x).end()
// 笔者更推荐 begin(x), end(x)
#define rall(x) (x).rbegin(), (x).rend()

// #x 将使用双引号包裹参数 x (MSVC 还有单引号的 #@)
#define reopen(x) { freopen(#x".in", "r", stdin); freopen(#x".out", "w", stdout); }

使用 #undef 取消文本替换宏,意义为「仅在包裹的代码段落中执行替换」 。

C++ 预定义了一些宏,它们会随着「情况」的不同发生改变。如 __cplusplus 指代当前语言标准, __LINE__ 将指代当前行,__FUNC__ 将指代当前所在的函数。此处不作展开,详情请参见 cppreference 预定义宏。配合这一点,可以写出一个简单的调试文本替换宏:

1
2
#define here log("Passing [%s] in LINE %d\n", __FUNCTION__, __LINE__)
// log 见下文输出调试信息处

条件宏 4

4

https://en.cppreference.com/w/cpp/preprocessor/conditional

#ifdef, #else, #endif 之类的宏操作会在编译过程中选择某些部分编译与否等等。

在竞赛中最经典的使用是配合文件流重定向,在本机使用文件输入,而在 OJ 上使用标准读入。大部分 OJ 都定义了 ONLINE_JUDGE 的宏,因此可以借此作为开关。

1
2
3
#ifndef ONLINE_JUDGE
reopen(1); // 在上面已定义此宏。
#endif

编译时附加 -DLOCAL 来定义一个名为 LOCAL 的宏。

断言

这并非一个函数,而是一个宏。接受一个表达式,若为假程序将直接退出,_十分_便于调试。

例如,某区域是你永不希望到达的,你可以在此区域附加 assert(false)。也可使用 __builtin_unreachable() / std::unreachable()5 来标记不可达的区域。

5

https://en.cppreference.com/w/cpp/utility/unreachable

有的 OJ 会屏蔽 assert (事实上宏 NDEBUG 决定之),不过实现一个 6 也很容易:

6

这个宏参考了 f0re1gners 的模板库 https://f0re1gners.github.io/template/template.pdf

1
2
3
#ifdef ONLINE_JUDGE
# define assert(condition) do if (!(condition)) exit(*(int*)0); while (0)
#endif

!~ 命名空间 7

7

https://en.cppreference.com/w/cpp/language/namespace

namespace 是 C++ 中十分有效的解决命名冲突的手段。 假设现在要处理两段命名冲突的数据,将其命名为类似的名称似乎并没有想象中的美观。这种情况下,可以借助 namespace 将其包裹住。

1
2
3
4
int _hash[maxn], _hash_2[maxn];

namespace L { int _hash[maxn]; }
namespace R { int _hash[maxn]; }

使用时:L::_hash[i], R::_hash[i]

欲指代全局的命名空间(如 C 库函数),可直接使用 :: 打头,如 ::abs(a)。

匿名的空间也是可行的,对于 namespace {int xxx;} 的访问可直接 ::xxx。

C++ 标准库函数都位于 std 中,这意味着若要使用他们,则需 std::xxx 指代它们。不少竞赛选手常常喜欢使用一句多为人诟病8using namespace std,这句话将在全局引用 std 命名空间。

8

https://stackoverflow.com/questions/1452721/why-is-using-namespace-std-considered-bad-practice

如果把 ranges::views::iota_view 比如文件路径 /home/patricky/Downloads/files,那么 using namespace 的意义就如同将其置于环境变量中,意味着你可随时随地使用其下的文件。使用此方法之后,你应当更小心地使用变量名。

另外一种不错的手段是,使用时再 using。如 using std::cout 之后即可 cout << "Anything you want." 了。

说起来,如此长的命名空间 std::ranges:views(尽管它已被别名为 std::views) 实在太不方便了,用下面的语法来给它起个别名:

1
namespace NV = std::ranges::views;

作用域 -- 另一个解决命名冲突的方法

一个花括号围住的内容。

1
2
3
4
5
6
7
8
9
int n = 1;
{
using std::cout, std::cin;
// C++17 开始可以一句话 using 多个目标
cout << n << "\n"; // 1
int n = 2;
cout << n << "\n"; // 2
cin >> n;
}

运算符的重载 9

9

https://en.cppreference.com/w/cpp/language/operators

简单说来就是将某函数绑定到某运算符上(优先级与之前一致),常见的如定义一个_不小 10_ 的矩阵:

10

小矩阵请直接使用 array<array<int, N>, N>

1
2
3
4
5
6
7
8
template<class T = int>
struct Matrix {
Matrix(int _r = {}, int _c = {}) : r{_r}, c{_c}, data(r * c) { }
T* operator[](int i) { return & data[i * c]; }
private:
int r, c;
vector<T> data;
};

这样之后就可以对 Matrix m(2, 2) 使用 m[1][0] 了。

写一个比较器(通常是重载小于号)再传入 STL 容器中,这种时候,函数的头就十分重要:

1
2
3
4
5
struct node {
int _val;
// 不一定需要有「小于」的意义。
bool operator<(const node &_) const { return _val > _.val; }
};

仿函数 / 函子

重载括号之后,使用括号运算符的感觉就像是函数一样。

如果想用 unordered_{, multi}{map, set}<pair<T1, T2>, T3>,可以传一个仿函数进去,如 unordered_map<pair<int,int>,int, your_hash>

或直接特化 hash

1
2
3
4
5
6
7
8
template<class T1, class T2> 
struct hash<pair<T1, T2>> {
size_t operator()(const pair<T1, T2> &t) const {
return t.first ^ (t.second << 1);
}
};

unordered_map<pair<int, int>, int> ump;

不过其实不如写一个函数将 pair<int, int> 先哈希一次,再用这个数字来哈希,详情请见:

Blowing up unordered_map, and how to stop getting hacked on it - Codeforces

* Lambda 表达式 / 匿名函数 / 闭包 11

11

https://en.cppreference.com/w/cpp/language/lambda

简单来说即「函数中的函数」,然而其类型两两不同(哪怕写的一模一样)。一个 lambda 表达式包含如下几个部分:

1
2
3
4
5
6
7
[] // 捕获列表,包含两种捕获方式:按值(const)/引用。将会拷贝一份至 lambda 对应的类中。
// 如 [a] 表示按值捕获,[=] 表示「全部」按值捕获
// 而 [&a] 则为按引用,[&] 是其全称形式
() // 参数列表,如果为空可以省略。
各种说明符 // (可选)试图修改按值捕获的变量时应当使用 mutable
// 可以使用 -> type 作为尾置返回类型的说明
{} // 函数体

因此_最简单的_ lambda 应当是:[]{}。为便于多次调用,可以使用 auto 自动推导其类型:

1
2
auto sayHell = [] { cout << "Hell World!"; };
sayHell();

来看标准库在后续版本对 lambda 表达式做的一些加强:

  1. C++14 起:lambda 表达式可以是模板了,但只能通过在类型中写 auto
1
2
3
4
5
6
auto foo = [](auto cmp, auto a, auto b) {
cout << (cmp(a, b) ? "YES\n" : "NO\n");
};

// less_equal<int>{}
foo([](int a, int b) { return a <= b; }, 1, 2);
  1. C++14 起:带有捕获初始化的 lambda,可以在捕获的时候给初值了。
1
2
3
4
5
6
//                      x ++ (错误,必须带等号)
int x = 4, y = [&z = x, x = x + 1] {
z += 2;
return x * x;
} ();
// x, y = 6, 25

这使得 bind 的使用场景越来越少。

  1. 今天的 lambda:若干 this / *this,支持 <> 定义模板 lambda 了等等,此处从略。

来写个递归的 lambda (尽管 C++23 可以 this auto self 了):

1
2
3
4
5
auto fib = [](auto &&self, int n) -> int { 
return n <= 2 ? 1 : self(self, n - 1) + self(self, n - 2);
};

fib(fib, 5);

配合上述 C++14 特性,可以写一个即刻调用的版本:

1
2
3
[&, fib{[&](auto &&self, int n) -> int {
return n <= 2 ? 1 : self(self, n - 1) + self(self, n - 2);
}}] { return fib(fib, 5); };

这样写_有点_麻烦,这主要是因为写下 fib 这名字时类型未定,如果有个什么工具标注一下类型就_好_了,有请 ——

函数对象 function

function 能够接受若干「可执行的对象」1213

12

https://en.cppreference.com/w/cpp/named_req/Callable 13: https://en.cppreference.com/w/cpp/utility/functional

对于上面的例子,依然需要在定义时给出类型以及返回值:

1
2
3
4
5
function<int(int)> fib = [&fib](int n) -> int { 
return n <= 2 ? 1 : fib(n - 1) + fib(n - 2);
};

cout << fib(5);

其他时候的 function 就自由得多。下面的例子演示了如何使用 function 存储单个参数的三个函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <functional>

void anything(int = 1) { }

auto main() -> int {
function foo = [](int = 1) {};

foo = anything;

namespace NP = placeholders;
foo = bind(greater<int>{}, 1, NP::_1);

return {};
}

折叠表达式 14

14

https://en.cppreference.com/w/cpp/language/fold

要使用这一特性,请使用 C++17 及以上语言版本。

此处不展开了,本身是个_不太复杂_语法。来看两个笔者常用的简单功能:

输出 tuple

apply(a, t) 将会把 t 拆包作为「可运行的」a 的参数,另外逗号表达式严格自左向右执行,算是这段代码中最有技巧性的部分。

1
2
3
4
5
6
7
template <typename... Ts>
ostream& operator<< (ostream &os, const tuple<Ts...> &tp) {
apply([&os](const auto &...args) { ((os << args << " "), ...); }, tp);
return os;
}
// ...
cout << tuple{1, 1.F, "anything", tuple{1, 2, 3, "hello"}};

输出调试信息

为了利用众所周知的 cerr15clog16,来写一个函数:

15

https://en.cppreference.com/w/cpp/io/cerr 16: https://en.cppreference.com/w/cpp/io/clog

1
2
3
4
5
6
7
8
template <typename... Args>
#ifndef ONLINE_JUDGE
void log(const Args &...args) { ((cerr << args << ", "), ...); }
#else
void log([[maybe_unused]] const Args &...args) { }
#endif

log(__LINE__, "ans = ", ans, "-----\n");

其中包含 C++17 引入的 [[maybe_unused]] 能让编译器对没有使用的参数「闭嘴」。

什么?你更喜欢古老的 printf 系列?请用(略去了条件编译):

1
2
3
#define log(fmt, args...) fprintf(stderr, fmt, ##args)

log("Reached [%d], ans = %lld", __LINE__, ans);

节省或浪费生命的小玩意

这一部分将介绍一些笔者认为十分方便的语法、工具。

超短的小语法、位运算等

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
exit(0)  // 在任意位置结束程序,然后去世

0X3F // 十六进制
077 // 八进制
0B11 // 二进制(C++14)
1'000'000'007 // 分隔符(C++14)

!!x; // 为 1 表示 x 不是 0
!x; // 与上面相反
~x; // 有符号数 x 不是 -1 (这是因为 -1 补码全 1)
~0U // 大数
(x & 1) // 为 1 表示 x 是奇数 a + b & 1 判定的是 (a + b) 整体
(~x & 1) // 与上面相反,但如果使用等价的 (x & 1 ^ 1) 则会报警告
(x >> 1) // x / 2 取下整(而并非 / 2 表现为向零取整)
(x << 1) // x * 2 当心溢出
// x * 10 = x * 8 + x * 2 = (x << 3) + (x << 1)
// 一个自作聪明的小技巧

x & -x // lowbit => 易知 lowbit(x) == x 的数或为 0 或为 power(2, k)
x & (x - 1) // 最低位归零 => 易知 (x & (x - 1)) == 0 意义同上

(1LL << x) // power(2, x) 当心溢出
__lg(x) // 二进制长度 (这里要提一下 __builtin_ 一族了)
// 参见 https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html
// 以及 C++ 20 头文件 <bit>

"3124"[x] // 输出这个 const char[5] 中 x 下标对应的字符 当心越界
// 通常用作 " \n"[i == n]

(i & 1 ? odd : even).push_back(x); // 三目运算符真好用
set(A.begin(), A.end()).size(); // A 中的元素种类数

一些常用的循环

枚举所有状态

O(n×2n)

1
2
3
4
5
6
7
for (int i = 0; i < 1 << n; ++i) {
for (int j = 0; j < n; ++j) {
if (i >> j & 1) {
// selected j
}
}
}

枚举子集

O(2popcount(n))

1
2
for (auto i = s; i; --i &= s) { } // 从大到小
// 使用 s - i 就是从小到大。另外上面没算空集,额外判下

枚举 n 个选 k 个的集合

(kn​)

1
2
3
4
for (int t = (1 << k) - 1, x, y; t < 1 << n; t = ((t & ~y) / x >> 1) | y) {
f(t);
y = t + (x = t & -t);
}

枚举倍数

O(logN)

1
for (int j = i + i; j <= N; j += i) { }

输出变量的类型

一个_看似很有用的_东西。

1
2
3
#include <cxxabi.h>
// ...
cout << abi::__cxa_demangle(typeid(x).name(), {}, {}, {}) << "\n";

y 组合子 —— 即刻使用的递归 lambda

选自 A Proposal to Add Y Combinator to the Standard Library 17

17

"这段代码具体什么意思?" https://riptutorial.com/cplusplus/example/8508/recursive-lambdas

1
2
3
4
5
6
7
8
9
10
11
12
template <class Fun> struct y_combinator_result {
Fun fun_;
template <class T>
explicit y_combinator_result(T &&fun) : fun_(::std::forward<T>(fun)) {}
template <class... Args> decltype(auto) operator()(Args &&...args) {
return fun_(::std::ref(*this), ::std::forward<Args>(args)...);
}
};

template <class Fun> decltype(auto) y_combinator(Fun &&fun) {
return y_combinator_result<::std::decay_t<Fun>>(::std::forward<Fun>(fun));
}

使用案例:

1
2
3
cout << y_combinator([&](auto &&gcd, int a, int b) -> int {
return !b ? a : gcd(b, a % b);
})(10, 20) << "\n";

或者多次使用:

1
2
3
4
5
6
auto gcd = y_combinator([&](auto &&gcd, int a, int b) -> int {
return !b ? a : gcd(b, a % b);
});

gcd(10, 20);
gcd(11, 11);

上述案例仅用于了解如何使用该语法。实际操作请使用 std::__gcdstd::gcd(C++17)。

高维 vector

尽管根据 CTAD,C++17 开始可使用 vector graph(n, vector(n, 0)) 来定义一个 n×n 的 vector,但当维数更多时,这种方法依然_不够_简洁。在 C++23 还未普及之前,下面的方法18 也相当不错:

18

这方法取自 https://codeforces.com/blog/entry/76149?#comment-606512

1
2
3
4
5
6
7
8
template <class... Args> 
auto ndvector(size_t n, Args &&...args) {
if constexpr (sizeof...(args) == 1) {
return vector(n, args...);
} else {
return vector(n, ndvector(args...));
}
}
1
2
3
4
5
6
7
// 维数越多越明显,但一般也就四维撑死了~
{ vector g(n + 1, vector(m + 1, vector(c + 1, 0X3F3F3F3F))); }
{ using vector; vector g(n + 1, vector(m + 1, vector(c + 1, 0X3F3F3F3F))); }
{ auto g = ndvector<int>(n + 1, m + 1, c + 1, 0X3F3F3F3F); }

// 造一个超级高维的 vector:
auto x = ndvector(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);

上面这个 x 展开是什么类型 19?无需在意。总之,_都_让编译器忙活去吧!

19

http://fars.ee/EsZ2

语法项

范围 for 20

20

https://en.cppreference.com/w/cpp/language/range-for

如,遍历一个 set<int> 在 C++11 之前使用迭代器:

1
2
3
4
5
6
7
8
9
static const int a[] = {1, 2, 3, 4, 5};

set<int> s(a, a + 5);
// C++11 可以 s{1, 2, 3, 4, 5}; 来初始化
// 也可以将下面 it 的类型写成 auto

for (set<int>::iterator it = s.begin(); it != s.end(); it ++) {
cout << *it << "\n";
}

在 C++11 使用 for (int i : s) 即可。在 C++20 还可在 for 中再定义一些变量:

1
2
3
4
5
6
7
8
9
10
11
12
for (auto S = "Hi!"; const auto i : s) { }

// 一些其他的小技巧
for (int i : {1, 2, 3, 4, 5}) { }

vector a(n, 0); // CTAD
for (int &i : a) { cin >> i; }

vector p(m, pair{0, 0});
for (auto &[x, y] : p) {
cin >> x >> y;
}

if / switch 中定义变量 21

21

https://en.cppreference.com/w/cpp/language/if

要使用这一特性,请使用 C++17 及以上语言版本。

从语义上来说,确实更方便了,但也许会部分编码者抓狂。

1
2
3
4
5
if (int op = read(); op == 1) {
// ...
} else if (op == 2) {
// ...
}

auto22, 初始化 23, CTAD24, 结构化绑定 25

22

https://en.cppreference.com/w/cpp/keyword/auto 23: https://en.cppreference.com/w/cpp/language/list_initialization 24: https://en.cppreference.com/w/cpp/language/class_template_argument_deduction 25: https://en.cppreference.com/w/cpp/language/structured_binding

C++11 的到来使得这个语言充满活力,尤其是 auto 的拓展。聚合(或其他)初始化则更加配合这一点,一些经典的例子是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
auto /*占位*/ do_nothing = []{}; // 承载 lambda

template<class T>
auto sum_up(T a, T b) { return a + b; }
// C++20 简写模板可写的更简洁

int arr[10]{}; // 初始化

auto main() -> int { }

// initializer_list<int>
// C++17 起这里一定带等号
auto init_list = { 1, 2, 3, 4, };

decltype(b) a;

配合后续版本增加的特性,auto 更为通用:

结构化绑定

要使用这一特性,请使用 C++17 及以上语言版本。

绑定数组、tuple-like、或者只是一个普通的结构体:

1
2
3
4
5
6
7
8
9
static int arr[] = { 1, 2, 3, 4 };
auto &[a, b, c, d] = arr;
c++;

const auto [x, y, _] = tuple{1, "anything", 1.F}; // CTAD

struct point { int x, y; };
auto [x, y] = point{1, 1};
// 然而笔者感到遗憾的是,还不支持形如 auto [[x, y], r] 的语法

CTAD

要使用这一特性,请使用 C++17 及以上语言版本。

再也不需要写全类型了,如 pair<int, double> t { 1, 1. } 可直接写为 pair t{ 1, 1. } 26

26

1.double 类型而 1.Ffloat 类型

vector<vector<int>> a(n) 也可写为 vector a(n, vector(0, 0))

1
2
3
4
5
for (auto [dx, dy] : {pair{0, 1}, {0, -1}, {1, 0}, {-1, 0}}) {
int x = dx + px, y = dy + py;
} // 遍历方向

auto mp = map{pair{1, 1}, {2, 1}, {-1, 1}};

输入一行带空格的字符串 27

27

https://en.cppreference.com/w/cpp/string/basic_string/getline

对一个 char[] 可使用正则表达式 scanf(" %[^\n]", str)28

28

https://en.cppreference.com/w/c/io/fscanf

1
2
string s;
getline(cin, s);

注意会吃掉 '\n' 。读者可尝试以下代码:

1
2
3
4
5
6
7
int foo; 
cin >> foo;

// cin.ignore();
string s;
getline(cin, s);
cout << quoted(s) << "\n";

多个字符串黏起来的字符串

当你的代码中出现了一行很长的字符串,你可以将它们分隔开来,就像这样:

1
2
3
const char *str = "Hey! "
"What? You have a new line! "
"How you did that? ";

原始字符串 29

29

https://en.cppreference.com/w/cpp/language/string_literal

不处理转义字符的字符串。

1
2
3
4
5
string raw = R"(\n\r\b\t\

这些换行也不处理 ...

)";

一些好用的 STL 类 30/ 函数

30

为什么没有 pbds? 我实在是太懒了 ...

排序、乱序等

排序

1
2
3
4
5
6
sort(A.begin(), A.end());
sort(A.rbegin(), A.rend()); // 反着排

sort(A.begin(), A.end(), [](auto &a, auto &b) {
return ::abs(a) < ::abs(b);
});

已经有序

1
2
is_sorted(A.begin(), A.end()); // 非严格升序
is_sorted(A.begin(), A.end(), less_equal<>{}); // 严格升序

乱序

自 C++17 起 random_shuffle 被弃用,现在使用 shuffle

1
2
3
4
5
6
7
8
vector a {-6, 1, 2, 3, 4, 4, 5};
mt19937 rng{
chrono::steady_clock::now()
.time_since_epoch()
.count()
};
// 传 time(nullptr) 亦可
shuffle(a.begin(), a.end(), rng);

顺便看看 stable_sort

创建一个排列

1
2
iota(A.begin(), A.end(), 1); // 1, 2, 3, ...
iota(A.rbegin(), A.rend(), 0); // n - 1, n - 2, ..., 2, 1, 0

累积 accumulate

求和 / 积、或者是其他操作,下面是两个例子:

1
2
3
accumulate(A.begin(), A.end(), 0LL); 
// 注意不要传入 0 使用 int+ 带来不该的溢出
accumulate(A.begin(), A.end(), 0, bit_xor<int>{}); // 异或和

求最值

m{in{, max}, ax}{_element} 都可以传比较器,如 min(a, b, cmp)

有的时候是不同类型做操作,可以追加类型:min<long long>(0, b)

可以传 initializer_list,如 max({a, b, c, d, ...}) 不过这实际上也是_非常_蛋疼的一点 ,意味着 min 是二义性的,想封装一个 Sparse Table 板子的话需要注意这一点。

1
2
3
4
5
6
7
8
max(max(max(a, b), c), d);
max({a, b, c, d});

// initializer_list
auto A = {-6, 1, 2, 3, 4, 4, 5};
auto [m, M] = minmax(A);

tie(m, M) = minmax({ i, i * m, i * M });

离散化

1
2
sort(A.begin(), A.end());
A.resize(unique(A.begin(), A.end()) - begin(A));
1
2
3
4
5
sort(c + 1, c + 1 + tot);
tot = unique(c + 1, c + 1 + tot) - (c + 1);
for (int i = 1; i <= n; ++i) {
a[i] = lower_bound(c + 1, c + 1 + tot, a[i]) - c;
}

其他

包括但不限于

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
count{, _if}()
generate{, _n}()
fill{, _n}()
copy{, _if}()
{lower, upper }_bound()
equal_range()
binary_search()
nth_element()
partial_sum()
adjacent_difference()
{, inplace_}merge()
reverse()
mismatch()
rotate()
__gcd()
gcd() (C++17)
lcm() (C++17)
inner_product()
transform()
{all, any, none}_of()
{is, next, prev}_permutation()
basic_string

效率不是非常高的(甚至可以说很差):

1
2
3
valarray
regex
complex (这个算吗...)

* 其他注意事项

  1. I/O cin.tie(nullptr)->sync_with_stdio(false)。具体原因可直接百度或查看 cppreference ios_base::sync_with_stdio 以及 cin 相关文档。看完这些,读者也应同时理解了为什么 endl 那样「慢」。

  2. vector::iterator 可以直接当作指针用。(RandomAccessIterator),比如 V.end()[-1], V.rbegin()[0]。本质上就是 i[x] 等价于 *(i + x)

  3. 知道 vector 的大小,要么直接开,要么先 reserve 一下再 {push, emplace}_back

  4. vectorresize / clear 方法之后,记得 shrink_to_fit31

  5. 一些没有 clear() 成员方法的容器(queue, stack, ...)可以 decltype(cap){}.swap(cap) 流放之。

  6. array 外,所有容器的 swap 方法都是 O(1) 的。

  7. 在栈上定义的 array 开在栈上,而 vector 对象开在栈上,数据开在堆上。

  8. 注意该不该引用(能不能用右值引用续命),能不能移动(如果可以尽可能使用 move

  9. multi{set, map}::count 的复杂度是 O( 出现次数 +log(n) ,而 C++20 的 contains 则是 O(logn)

  10. size_t 类型应强转,或使用 C++20 的 ssize() 获取长度。

  11. 用迭代器边遍历 set 边删除迭代器时应使用 it = S.erase(it) 来代替 it ++。也就是

1
2
3
4
5
6
7
for (auto it = s.begin(); it != s.end(); ) {
if (!judge(*it)) {
it = s.erase(it);
} else {
it ++;
}
}
  1. set::merge(t) 是直接把不同于两集合的元素从 t 直接薅走,意义与 set::insert(t.begin(), t.end()) 不总一样。

31

https://en.cppreference.com/w/cpp/container/vector/shrink_to_fit

这个条目等我下次破防再更新

本文即将移除的条目

程序计时

笔者一直主观不赞成将计时嵌入至代码的行为,只是单文件、单元测试,完全可以使用系统的计时工具:

1
time
1
2
(Measure-Command {xxx | Out-Default}).ToString()
Measure-Command {cat 1.in | .\A | Out-Default} | findstr TotalMilliseconds

后记、致谢、贴贴

  • 感谢 @MeteorZ 对 「判断是二的幂」 做的 「特判 0 」 补充。
  • 感谢 @Lhgzbxhz 对「断言」做的「__builtin_unreachable()」补充。
  • 感谢 @阿汤 对「判断是二的幂」做的「(x&(x-1))==0」补充。
  • 感谢 @轻狂书生 对「程序计时」做的催更。
  • 感谢 @soulmate 对删除「异常处理」与「std::regex」内容的建议。
  • 感谢 @n-WN 对「预定义宏」的补充。

changelog

UPD: 本文已经从「算法竞赛中常用的语法操作」重命名为 「算法竞赛中的 C++ 语法操作」。

UPD(22-09-16): 这次更新给每一个条目都加上了 cppreference 的链接(如果有)并重新规划了文章结构。

UPD(22-09-30): 移除了「一些位运算」条目。增加了 y 组合子。

UPD(23-04-10): 纠正了 C++14 lambda 相关描述,增加了一个即刻调用的递归 lambda 写法。并附上了本文的 markdown 版本。能坚持到现在真是一个奇迹,希望下次更新不会太久。

  • 本文作者: FXJFXJ
  • 本文链接: https://fxj.wiki/posts/7a219ca8/
  • 版权声明: 本博客所有文章除特别声明外,均采用 ZERO 许可协议。转载请注明出处!