小技巧整理
set/map/pbds::gp_hash_table
通常情况下:set < map < gp_hash_table。
PNR #4 A,就是改用了 gp_hash_table 才过了。
gp_hash_table 的使用,见 pb_ds 库
构造函数的时间复杂度
是 \(O(\cfrac{size}{\omega})\) 的。分析的时候不要漏了它。
关于 string 的 operator
- 不如 +=。+ 会返回一个字符串,+= 只是拼接。
关于除法
在 3th ucup 的 E 题中,整除分块套整除分块我跑了 15s,佳神跑了 1s。他用了两个如下的卡常技巧:
第一个是,对于内层循环的计算约数个数。我们这样写是 \(4\) 个除法:
//n / 1 + n / 2 + ...
int ans = 0;
for(int l = 1, r; l <= n; l = r + 1) {
r = n / (n / l);
ans += (r - l + 1) * (n / l);
}
cout << ans << endl;
但是我们基于分段的思想(也就是 \(1,2,...,\sqrt n\) 和 \(n / (1,2,...,\sqrt n)\))。我们要求的东西是,\(a*b\le n\) 的组数。我们这样算:
//n / 1 + n / 2 + ...
int ans = 0; int sq = sqrt(n);
for(int a = 1; a <= sq; a ++) ans += (n / a); //a \le sq
for(int b = 1; b <= sq; b ++) ans += (n / b - sq); //a \ge sq
cout << ans << endl;
然后可以把 \(sq\) 脱出来,进一步简化到只用一个除法:
//n / 1 + n / 2 + ...
int ans = 0; int sq = sqrt(n);
for(int a = 1; a <= sq; a ++) ans += (n / a);
cout << 2 * ans - sq << endl;
第二个技巧是,当除法的除数确定为 \(d\) 的时候,计算任意次 \(\lceil\cfrac{a}{d}\rceil\) 可以这样做到单次除法预处理,后面都不用到除法,只用到移动位数操作(移动位数的位数为 \(64\),这是最快的一种):
//calculate a / b
ull inv = ~0ull / b + 1;
cout << ll(__int128(a * inv) >> 64) << endl;
vector 不要放在结构体里面和结构体一起排序。理由如下:
会喜提 6 倍常数。
能用树状数组就不要用线段树。
对于 1e9 * 1e9 大小的加法,unsigned long long 可以 18 次 取模一次。
浮点数,可以看做是 真实值 \(\pm\) epsilon.
epsilon:
double 2e-52
long double 2e-64
__float128 2e-113 但是很慢。
对于若干次运算之后,只要结果一直可以保存进 double,依然是真实值 \(\pm\) epsilon,而不是叠加。
精度误差,来源于两个地方:
- 过程中出现了太大的数,然后做了减法,(除法没事)
- 做了 \(t\) 次运算之后误差为 \(\sqrt(t) \epsilon\)。
对于浮点数精确测算浮动到底多大:floating_point environment set round(简称 fesetround)
可以设置两种,一种一直向下舍入,一种一直向上舍入。FE_DOWNWARD, FE_UPWARD 两种。
格式:
fesetround(FE_DOWNWARD);
注意有个横杠。
两个程序,一个 down,一个 up,可以互相拍,算的是误差的两倍左右。如果这个拍出来没问题,那就是没问题。
终端里面,上箭头可以快速使用之前用过的功能。注意不要写死循环对拍,否则会使得你的编译器必须被你关掉。
强制停止某个程序:linux 下 ctrl + c。
use shuffle(v.begin(), v.end(), mt19937(time(0))) instead of random_shuffle.
对于 vector,特别是有 clear 的图,换成邻接表。
分块可能可以平衡复杂度。
卡常的时候,测试以下瓶颈是哪里,专心对着瓶颈卡常。
内存连续访问很重要,所以如果瓶颈的位置内存没有连续访问,要尽量让它连续访问。
对于只计算偶数/有 mod 4 = 1/3 但是只统计 1 的某个东西,可以考虑计算 0 - 1 / 1 - 3 以及总数,这样可能会快 4 倍或 2 倍,或者使得这个东西能算。因为 a - b 在形式上更自然一些。
不知道哪里错了,就从小数据和极限数据里面找。
unused 取消掉:在变量后面加一个 __attribute__((unused))
,例如:
void circ3(int now,int l __attribute__((unused)),int r __attribute__((unused)),int k0,int x0,int b0){ k[now] += k0; b[now] += k0 * x[now] + b0; x[now] += x0; }