其他-杂乱总结

精度

计算 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 ight)) ,就用这个 (a_j) 去更新 (x_i) 。对 (b_j) 同理。一次合并的时间复杂度是 (O(k^2))(k) 小的话就可以视为常数,所以基本上不会破坏线段树的时间复杂度。

树上路径求交

求出两条路径的 (lca) 记为 (g, h) ,如果

  • 深度相同, (g e h) 则无交集, (g = h) 则有交集。
  • 深度不同,用深度较大的 (lca) 作为 (t) ,假设另外一条路径 ((x, y)) ,则 (t) 应在 ((x, y)) 上才有交集,即:

[lca(t,x) = t igvee lca(t,y) = t ]

当两条树上路径有交集时,画图并且分 (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 下换行符是 ' ' 。不要用“是换行符”作为输入结束的标识,而应该用“不是合法字符”

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
", 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);

的后果是调试到死。

原文地址:https://www.cnblogs.com/ghcred/p/9494337.html