其他-杂乱总结
精度
计算 x 的位数的代码是 int bit = log(x + 0.5) / log(10) + 1;
或者 int bit = ceil(log(x) / log(10)) + 1
。即先算出 double 类型的答案,向上取整后 +1 ,再存到 int 类型里。
如果错写成 log(x) / log(10) + 1
,对于 x = 1000,(在我测试的电脑上)会计算出 3,而正确结果是 4 。
紫书里面提到了,对某个数开根要写成 int tmp = sqrt(x + 0.5)
,否则有精度差。
就是这两个地方,注意写的时候 +0.5 来避免类似 x.99999828 的数被截尾算作 x (即避免精度差)。
vector 存边
最常见的边遍历
vector<edge>::iterator it;
for (it = G[x].begin(); it != G[x].end(); ++it) {
//...
}
有时候会写成
for (i = G[x].size() - 1; i >= 0; --i) {
//...
}
注意这里 i 必须是 int ,如果 i 是 long long 就必须写成 i = (long long)G[x].size() - 1,否则会出些奇怪的错误。所以最好直接给 i 开 int 。
可以测试一下:
int tmp = 5;
printf("%lld", tmp - 1);
结果不是 4 。
printf("%lld", 5 - 1);
结果也不是 4 。
upd: 用笔记本试了下,能跑出 4 ,但是机房电脑不会。反正避免就是了。
fread
甚至强过头文件优化(雾)。使用方法同 getchar()
函数。
char buf[1 << 20], *p1, *p2;
inline char void gc()
{
return p1 == p2 && (p2 = (p1 = buf) + fread(buf,1,1<<20,stdin), p1 == p2) ? EOF : *p1++;
}
next 数组
$ k = 0, 1, 2, 3 $ 分别表示上下左右,然后希望找一个对应关系 f(上) = 下, f(左) = 右,反过来也是。这样的话这个对应关系 \(f(k) = k\ xor\ 1\) 。
求区间严格第 \(k\) 大值
正解是树套树,裸题有“二逼平衡树”。但是有的问题 \(k\) 比较小,直接线段树维护就可以了。假设左区间前 \(k\) 大值分别是 \(a_1,a_2,a_3...\) ,右边是 \(b_1,b_2,b_3...\) 。合并后当前区间的前 \(k\) 大值是 \(x_1,x_2,x_3...\) 。
考虑求 \(x_i\) ,枚举 \(a_1,...,a_i\) 和 \(b_1,...,b_i\) 。如果 \(a_j < x_{i-1}\ \left(1 \leq j \leq i\right)\) ,就用这个 \(a_j\) 去更新 \(x_i\) 。对 \(b_j\) 同理。一次合并的时间复杂度是 \(O(k^2)\) , \(k\) 小的话就可以视为常数,所以基本上不会破坏线段树的时间复杂度。
树上路径求交
求出两条路径的 \(lca\) 记为 \(g, h\) ,如果
- 深度相同, \(g \ne h\) 则无交集, \(g = h\) 则有交集。
- 深度不同,用深度较大的 \(lca\) 作为 \(t\) ,假设另外一条路径 \((x, y)\) ,则 \(t\) 应在 \((x, y)\) 上才有交集,即:
当两条树上路径有交集时,画图并且分 \(5\) 类情况(考虑根的位置)可知,\((a, b)\) 和 \((c, d)\) 这两对树上路径的交的两端是 \(lca(a,c), lca(a,d), lca(b,c), lca(b,d)\) 这四个点里,深度最大的两个点。
树上 LCA 关联的点
有时候边权化点权会用到,肯定可以算出深度之后重新向上跳,不过树剖的时候也可以这么搞:
while (Top[a] != Top[b]) {
if (Depth[Top[a]] < Depth[Top[b]]) {
b = Dad[xb = Top[b]];
} else {
a = Dad[xa = Top[a]];
}
}
if (Depth[xa] == Depth[lca] + 1)
insert(xa);
if (Depth[xb] == Depth[lca] + 1)
insert(xb);
if (a != b)
insert(Heavy_son[lca]);
树上换根对子树影响
假设原树的根是 \(x\) ,新根是 \(y\) 。讨论 \(t\) 在新树中的子树。
- 如果 \(y\) 就是 \(t\), \(t\) 在新树中的子树就是整棵树。
- 如果 \(y\) 在原树中 \(t\) 的子树外,那么新树中 \(t\) 的子树仍然是原树中它的子树。
- 如果 \(y\) 在原树中 \(t\) 的子树内,设 \(t\) 在 \(y\) 到 \(x\) 的路径上的儿子是 \(g\) ,而原树中 \(g\) 的子树是 \(s\) ,那么新树中 \(t\) 的子树是整棵树除去 \(s\) 这部分。
二分查找
STL 里有的容器(比如 set 和 map)自带 lower_bound()
,这是因为这些容器有一些非常优秀的结构,用 a.lower_bound(val)
可以利用到这些结构。因此这样做的速度远快于单纯的二分查找 lower_bound(a.begin(), a.end(), val)
。
图论题小细节
- 无向边用链式前向星存的时候记得开两倍边的空间。
- Tarjan 缩点之后用 Topsort 搞入度之类的东西的时候,记得考虑自环和重边——自环往往对入度并不应该有贡献。
- 看清无向图还是有向图
(因为这个爆零两次了)。
子集求和
从 nodgd 的一场训练赛里学到的新技能。就是 \(O(n \cdot 2^n)\) 算 \(f(S) = \sum f(T)\ (T \subsetneq S)\) 。
实际上就是一个多维前缀和。比如说,二维前缀和可以看做先算每行的前缀和,然后再算新的矩阵中每列的前缀和。同理,多维前缀和依次算每一维(代码中的 i )的前缀和。
听说有个叫快速莫比乌斯变换的东西 Orz 。
for (int i = 0; i < M; ++i)
for (int j = 0; j ^ FUL; ++j)
if (j >> i & 1)
f[j] += f[j ^ 1 << i];
换行符
爆零警告: Linux 下换行符是 '\r'
。不要用“是换行符”作为输入结束的标识,而应该用“不是合法字符”
C++11 新特性(注意 NOIp 不能用,省选不一定能用)
mt19937
据说可以得到 unsigned int
范围内的随机数。注意 time(NULL)
在对拍时由于程序跑得太快,可能会使两个程序有相同的 seed 。 chrono
库提供了更精确的计时函数,所以基本上不会出现上面说的情况。
#include <cstdio>
#include <chrono>
#include <random>
using namespace std;
mt19937 mand(chrono::steady_clock::now().time_since_epoch().count());
int main(){
printf("%u\n", mand());
return 0;
}
tuple
制作 tuple
tuple<int, int, int, int> tp = make_tuple(a, b, c, d);
解开 tuple:
int a, b, c;
tie(a, b, c, ignore) = tp;
或者
int a, b, c;
a = tp.get<0>();//等价于 a = get<0>(tp);
b = tp.get<1>();
c = tp.get<2>();
编译命令
- 本地有庞大的递归调用需要加
-Wl,--stack=一个很大的数
,不然并查集都可能会爆栈。 - 开启 C++11 需要加
-std=c++11
。 - Lemon 在 32 位下评测时需要加
-m32
以生成 32 位代码。
链式前向星的清空
只需要清空 first 数组就可以了,有的时候这会大大影响程序效率。
主席树
记住每次新建,写
if (!p)
new_node(p);
的后果是调试到死。