技巧合集

Lambda表达式

首先,Lambda读作 [‘læmdə]。

好,接下来我们步入正题。就我个人认为,Lambda表达式是一种能让一些不会经常调用的 简单函数 写得更简单的方法,其中最典型的例子就是sort自定义排序函数。
假设我们有一个结构体node如下:

1
2
3
struct node{
int x, y;
}points[114514];

一般来说,我们会写一个cmp函数如下:

1
2
3
4
bool cmp(node &a, node &b) {
if (a.x == b.x) return a.y < b.y;
return a.x < b.x;
}

或者更高级一点,使用operator重载运算符:

1
2
3
4
5
6
7
struct node{
int x, y;
const bool operator<(const node &b) const {
if (x == b.x) return y < b.y;
return x < b.x;
}
}points[114514];

但是,这些写法都是需要额外再开一个函数的,并且对于先按某规则排序再按另一种规则排序这种情况的处理并不好(第二种写法甚至不能在这种情况下使用)。于是我们需要一种更简单的方法来写排序函数——Lambda表达式。先看效果,它写出来大概长这样:

1
sort(points+1, points+n+1, [](node a, node b) -> bool { return (a.x==b.x?a.y<b.y:a.x<b.x); });

* 还能再简单点吗?
* 可以,那么就是这样:(下面代码中的-> bool被省略了)

1
sort(points+1, points+n+1, [](node a, node b) { return (a.x==b.x?a.y<b.y:a.x<b.x); });

这只是其中一种使用方法。借此,我们还可以把局部变量当做“全局”变量来用(并不是真的全局,但是在Lambda表达式的函数内可以使用这些局部变量),具体实现方法是 捕获 (这东西我还没想到在OI中的应用),也就是在那个[]内部做文章,实现可以参考参考资料。

upd 20231130

除此之外,Lambda表达式还可以在函数内部定义函数,例如,我们可以在 main 函数里定义一个 add 函数用来给链式前向星加边:

1
2
3
4
5
6
7
8
9
10
11
12
13
struct node{
int nxt, to;
}g[200010];
int head[200010], tot;

int main() {
function<void(int, int)> add = [](int u, int v){
g[++tot] = {head[u], v};
head[u] = tot;
};
add(1, 2);
return 0;
}

而这个函数也是受“变量”的定义域所影响的,例如下面在 test 函数中调用 add() 函数将出现 $\text{CE}$ :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct node{
int nxt, to;
}g[200010];
int head[200010], tot;

void test() {
add(1, 2);
}

int main() {
function<void(int, int)> add = [](int u, int v){
g[++tot] = {head[u], v};
head[u] = tot;
};
test();
return 0;
}

不仅如此,我们甚至可以在这基础上写递归(以 Tarjan 为例):

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
32
33
34
35
36
37
38
39
struct node{
int nxt, to;
}g[200010];
int head[200010], tot;

int dfn[200010], low[200010], cnt;
int st[200010], top;
bool inst[200010];

int scc[200010], sc;

int main() {
function<void(int)> tarjan = [&tarjan](int u) {
dfn[u] = low[u] = ++cnt;
st[++top] = u, inst[u] = 1;
for (int i = head[u]; i; i = g[i].nxt) {
int v = g[i].to;
if (!dfn[v]) {
tarjan(v);
low[u] = min(low[u], low[v]);
} else if (inst[v]) {
low[u] = min(low[u], dfn[v]);
}
}
if (dfn[u] == low[u]) {
sc++;
while (st[top] != u) {
scc[st[top]] = sc;
inst[st[top]] = 0;
top--;
}
scc[st[top]] = sc;
inst[st[top]] = 0;
top--;
}
return ;
};
return 0;
}

其主要就是一个 function 的实现,语法是 function<返回值类型(参数类型1, 参数类型2, ...)> = [捕获](传参){函数主体} ,而要递归就只用在捕获处引用这个函数(就像上面的例子)。

auto (C++11新语法)

先说怎么启用C++11或其他新版本,对于Dev-C++,在编译选项中的“在编译时添加这些参数”添加-std=c++11,这里的 11 可以换为其他版本如:-std=c++23
auto 可以自动匹配变量类型,但是没多大用(除非使用迭代器iterator这种难写的类型),具体而言可以这么写:

1
2
auto i = 1ll;
auto j = mp.begin(); // mp是一个map<int, int>

(值得一提的是,使用迭代器遍历map遍历出来的是pair类型的东西)
并且,auto还有一些可以减少代码量的用法,例如遍历vector:

1
2
3
4
5
6
7
for (int i = 0; i < (int)vec.size(); i++) {
cout << vec[i] << endl;
}
// 等价于
for (auto &i : vec) {
cout << i << endl;
}

同理,其他能使用迭代器的容器都可以如此遍历(但是,栈和队列都不行)。

using

这个语法很简单,应用只有一个——using ll = long long
它能定义 lllong long 并且还不能批量替换 int
如果要把代码中所有 int 替换为 long long,请使用 #define int long long (虽然这是未定义行为).

模块化思想 与 类和对象

将代码中实现不同功能的代码封装成函数,这会对 debug 有极大的帮助。
对象嘛,不是你想的那个对象,你可以使用 namespaceclass
给个例子:

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
32
#include <bits/stdc++.h>
using namespace std;

namespace work1{
string main(string t) {
return t+t+"adwe";
}
void print(string t) {
cout << main(t) << endl;
}
};

class work2{
private:
string main(string t) {
return t+t+"adwe";
}
public:
void print(string t) {
cout << main(t) << endl;
}
};

int main() {
// 使用namespace的方法
work1::print("1abc");
cout << work1::main("2abc") << endl;
// 使用class的方法
work2 *p = new work2; // 新建一个类
p->print("3abc");
p->main("4abc"); // 无法编译
}

调试常用

离线比赛常用:cerr

因为离线比赛中需要进行文件输入输出,但是每次开关文件很麻烦,而 cerr 可以不受文件输入输出流影响,往标准输入输出流里进行输出,但代价是每次输出都会清理一遍缓冲区,时间消耗极大,所以调试完后一定一定要把所有 cerr 语句删除。

啥都能用的 assert

位于头文件 cassert 中,如果不用万能头要把头文件导入。

在代码中可以写 assert(a <= n && 1 <= a); 来判断 a 是否在给定的范围内,如果不在程序将立即终止运行并输出错误所在的行号等信息,准确来说,只要 assert 语句中的表达式的值为 false ,就会返回报错。

可以使用宏 #define NDEBUG 即可一次性禁用所有 assert 语句。

值得注意的是,在洛谷中,如果 assert 返回报错会显示 RE ,并返回错误信息:

1
2
Runtime Error.
Received signal 6: Aborted / IOT trap.

参考资料

Lambda表达式