各种trick和细节错误汇总
这篇博客主要是用来记自己写代码的时候犯的各种小技巧和低级失误,好提醒自己,从而尽量缩短debug时间。
点分治
1.求每一个子树到重心的距离的函数接口应该是dfs2(v, eg, e[i].w)
而不是dfs2(v, now, 0)
。一方面是子树的父亲是当前的重心,而不是进入这一层点分治的点;另一方面别忘了考虑这条边的贡献。
2-SAT
1.建图一定要考虑对称性,即“如果\(x\)成立,则\(y\)必须成立”,那么我们还得考虑如果\(y\)不成立,则\(x\)也不能成立,比如[NOI2017]游戏。
哈希
1.觉得有必要的话最好写一个双哈希,防止出题人卡自然溢出。
2取模版哈希在算子串哈希的时候也别忘了取模,要不然就有负数了。
3.哈希表是真的快!尽量用哈希表代替map,防止被卡常。(比如[NOI2016]网格,刚开始用map有两个点怎么卡也卡不过去,改成哈希表后只用了300多ms)
主席树
1.查询的时候,判断是否往右走的条件是根据左区间而定,即t[t[now].ls].sum - t[t[old].ls].sum
,千万别手滑写成了t[now].sum - t[old].sum
。
线段树
1.较复杂的线段树一般需要推推式子才能知道要维护啥。推式子时有一个技巧,就是下标尽量都保证是全局的编号,而不是某一个区间中的第几个,这样区间合并的时候往往会简单的多,不用考虑两个区间的左右关系,比如[HAOI2012]高速公路。
数论,数学
1.万一遇到某一个题过程中要求组合数却不能取模,可以尝试取log,最后再用exp()
转换成真正的值,比如HDU4254 A Famous Game。
2.求两个数的lcm的时候为了防止爆int或long long,要先除以gcd,再乘以另一个数。
3.组合数一定要特盘\(m > n\)的情况,尤其是Lucas定理的时候!
lucas定理
1.在一篇博客上看到lucas定理可以不用递归,而是每次把\(n\)和\(m\)都除以\(p\)直到0。有如下代码
In ll C(int n, int m)
{
if(n > m) return 0;
return fac[n] * inv[n - m] % mod * inv[m] % mod;
}
In ll lucas(int n, int m)
{
if(m > n) return 0;
ll ret = 1;
for(; m; n /= mod, m /= mod)
ret = ret * C(n % mod, m % mod) % mod;
return ret;
}
高斯消元
1.对于每一次的主元,我们要找最大的数那一行,为了减小误差。不是最小的!
SAM
1.关于求每一个节点的endpos数量:插入的时候标记这个节点是一个结束位置,最后我们从下往上递归加一遍,就是每一个的endpos数量。有好长一段时间我都以为子树大小=endpos数量。后来才突然想明白,克隆出的节点的初值是0,因为他不是一个结束位置,所以我们求出来的每一个节点的endpos数量不等于子树大小。
高精度
1.高精乘低精是先乘再进位!先进位再乘还得有点别的运算。(好久不写调了半天,还看了我以前的博客)
树形背包
关于树形背包,我记得以前辟谣说的是\(O(n^2)\),不是\(O(n^3)\),但没想到我写的一直是假的\(O(n^2)\),在链的情况下还是会被卡成\(O(n^3)\)……所以这里贴一下树形背包的板子:
int siz[maxn], dp[maxn][maxn];
void dfs(int u)
{
... //dp初始化
for(int i = head[u], v; ~i && (v = e[i].to); i = e[i].nxt)
{
dfs(v);
for(int j = min(m, siz[u] + siz[v]); j >= 0; --j) //m是总体积
for(int k = max(0, j - siz[u]); k <= min(j, siz[v]); ++k) //上下界都要优化
dp[u][j] = max(dp[u][j], dp[u][k] + dp[v][j - k] + ...)
siz[u] += siz[v];
}
}
复杂度证明参考:树上背包的上下界优化
杂项
1.assert()
是一个debug用的好东西,当括号里的条件不成立时,会立即停止运行,并给出提示(在高级的oj上会告诉你),包含在<assert.h>
库里。
2.离散化数组一定要算好空间:比如把一堆区间离散化,就要开二倍。因为每一个区间的左右端点都要算进去。
3.看到异或,第一反应是每一位单独考虑!
4.clock()
是clock_t数据类型,返回的是long int。所以要是想保证程序运行时间小于0.9秒的话,必须强制转换成double,即if((double)clock() / CLOCKS_PER_SEC) < 0.9
(CLOCKS_PER_SEC类型也是long int)。
5.参见上一条,我们可以在程序开始的时候写一个double Beg = clock()
,结束的时候写一个doube End = clock()
,这样输出(End - Beg) / CLOCK_PER_SEC
就能知道程序的运行时间了。
6.sizeof函数不仅可以初始化数组,还可以显示一个数组的大小:直接输出sizeof(a)
就可以看到数组\(a\)占多少字节了。.p.s结构体也可以用。
7.printf("%0*d", b, a)
表示输出整数\(a\),若\(a\)的长度不足\(b\)时,高位用0补充。具体来说:
% 表示这是一个格式控制串 。
0 为填充字符,表示当输出长度不足指定长度时候,用 0填充不足的部分。
* 这个是指定输出宽度。(在scanf中表示忽略这个整数)
d 这个表示输出整数。
8.看到题目中出现二元组,可以想象成二维平面上的点。