小技巧整理

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,而不是叠加。

精度误差,来源于两个地方:

  1. 过程中出现了太大的数,然后做了减法,(除法没事)
  2. 做了 \(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; }

posted @ 2022-11-22 10:23  OIer某罗  阅读(35)  评论(0编辑  收藏  举报