编程习惯
先贴自己的缺省源
#include<bits/stdc++.h>
using namespace std;
#define rd(i,n) for(int i=0;i<n;i++)
#define rp(i,n) for(int i=1;i<=n;i++)
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define per(i,a,b) for(int i=b;i>=a;i--)
#define st string
#define vt vector
#define pb push_back
//#define int long long
typedef long long ll;
typedef long double ld;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);
return 0;
}
//Crayan_r
万能头,一点 for
循环和长数据类型的 #define
和 typedef
关同步流习惯只关 cin
。
关于 #define int long long
个人很反感这个东西,但是也不否认它在调试时的一些用处。
首先的坏处就是空间,不太想争论 64 位机下时间 int32
是否和 int64
等同,只能说这个问题很复杂,不能直接说“相等”,涉及寻址、操作和总线传输若干操作之间的优劣不一,是计算机底层原理,追求这个不如去写“指针的指针的指针的指针”。
还有违背了 C++
本身的约定有一种违背天条的感觉,并且 int
默认 \(32\) 位是为了兼容以前程序和方便程序员之间合并调试代码作出的规定,毕竟还是现在的主流。
最后就是真正的硬伤,在 \(\text{Topcoder}\) 和很多国际比赛中(包括 \(\text{CNOI}\) 中的交互题),都采用函数式交互的方式避免输入输出常数问题,但是有些(例如 \(\text{Topcoder}\))评测系统不会在答案文件中寻找强制类型转换后契合输入格式的函数,例如题目要求 void f(int a)
,写 void f(long long a)
就不会被评测系统检测到从而造成 CE,这在本地是测不出来的,因为本地自动调用会强转,没人会在赛时去写一个 xxx.h
的评测系统。
但是我的缺省源中依旧常年带着 //#define int long long
,因为在调试不知道哪里 WA 的时候随手打开来跑一下确定是不是边角炸 int
真的很好用。不过我一般会在找到问题之后再关掉,思考 int
是否会炸是编程中重要思维的体现。
关于代码封装
class
,struct
,namespace
别的不说,class
真香!尤其是在题目需要写好多棵(甚至 \(n\) 棵)主席树或者平衡树的时候,可以派上大用场。在写数据结构的时候,我的态度是能封则封,而且可以避免不同数据结构之间常用变量名重合的问题(例如 tree[]
)。
namespace
我主要用来写部分分包和封装网络流。网络流是我唯一习惯封装的非数据结构算法,因为其中有很多例如 vis
和 dep
还有图之类很常用的东西,有点难度的网络流题又基本都带点别的东西,所以网络流的封装才对我形成了习惯。
而且网络流封装还有一个好处就是契合网络流题目的模型化,把一种问题转化成标准的网络流问题,只用有限且单一的网络流工具接口就可以解决问题,保留了网络流建模这类题目本身的美感。
我比较习惯用 struct
的有矩阵和次大值。原因是 struct
相对 class
比较轻量级,而且写重载运算符比较方便。在矩阵中直接用 *
代替 multi(x,y)
,在次大值里直接维护 v
和 sv
,用 +=
往里面添加新数,用 +
来合并。在维护这些东西的大数据结构上好处最为明显。
曾经还迷过指针实现的数据结构,用 ->
直接操作并且不用考虑大小真的很爽,而且指针在很多时候很快。一开始了解到这种写法就是 CF19D 树套树被卡常,换成指针写法就过了。
但是后来知道 new
虽然全局动态非连续空间,但是在没有内存池的情况下很慢,开内存池又没有了不用考虑大小的优势。直接操作的弊端在学到区间修改线段树的时候就显露了出来——因为默认在本对象修改,当前函数没有任何和当前对象是谁有关的信息,pushdown
我不会了。
虽然后来又会了,但是也学到了新的东西——基于旋转的平衡树。旋转的代码在指针的情况下会变得非常长、不直观、难背且易错。所以也就渐渐抛弃了。
对了,在网上做题的时候,会用一个数论板子和多项式板子,都是自己写的,封装一些快速幂、逆元、卢卡斯、二次剩余、CRT、哈希表之类常用的东西,打 CF 用。多项式板子只有可怜的 FFT 和 NTT(现在 FFT 还有被 NTT 打入冷宫的趋势),因为这就是我可怜的知识储备。真正赛场上是不会闲着没事写向工程一样的数论 namespace
的,但是 NTT 可以考虑封装一下,分治 FFT 的时候好用。
码风
紧凑的,大括号不换行,字母之间不加空格,但是不重要,略过
初始化
多测的初始化,每次开头初始化需要用的部分。
普通线段树是一定要初始化的。
rmq
写 init()
函数初始化,lca
在 dfs()
函数内初始化。
如果需要处理阶乘及其逆元,或者组合数之类的,一定在 init()
函数内。
杂项
快速幂写递归式比较好记(个人而言),线段树分治建出图。
很多东西其实没有太严格的要求,比较自由,例如自动机和主席树写 struct node
还是直接用大数组模拟,完全看心情。
不过自动机有个小小的强迫症,就是能写路径压缩 \(\text{O(1)}\) 转移的一定不写均摊转移,问就是路径压缩的 AC 自动机是自己推出来的,比较有成就感。
说到路径压缩,直接路径压缩的 dsu 是我的习惯,按秩合并和启发式合并只在线段树分治以及需要卡常的时候用。dsu 还有一个小毛病就是 find(x)
写 head(x)
,而且常常忘了路径压缩(但是我曾经写了三个月的 dsu 都没加路径压缩,没出过问题,可见此类题目几乎没人卡)。