一些常数优化。

参考了宋佳兴的集训队论文。感谢 zzz,如果进集训队了算你一份。

其实啥都不懂,所以可能有很多错的地方。

通用优化方法

循环展开

  1. int 类型求和

直接写的瓶颈是,指令之间的跳转太多,以及指令之间的依赖关系。

比如 ffor(i,1,n) sum+=a[i]。可以选择把循环进行展开,以及通过某种方式增加并行:

for(int i=0;i<2000;i+=8) {
s0+=a[i],s1+=a[i+1],s2+=a[i+2],s3+=a[i+3];
s0+=a[i+4],s1+=a[i+5],s2+=a[i+6],s3+=a[i+7];
}
sum=s0+s1+s2+s3;
  1. int 类型加整数

按照 4 步进行循环展开。

  1. int 类型前缀和的计算

据说 NOI 应该写 ffor(i,1,n-1) pre[i+1]+=pre[i];

一种可行的并行:

int sum=0;
for(int i=0;i<2000;i+=4) {
sum+=a[i],a[i]=sum;
sum+=a[i+1],a[i+1]=sum;
sum+=a[i+2],a[i+2]=sum;
sum+=a[i+3],a[i+3]=sum;
}

不想轻易去改马蜂,所以直接手写循环展开把。 /xk

循环融合

把两个循环结合起来,可以大大减少内存的读写次数。

给了一个这样的例子:

dpi,j=min1k3dpi1,jk+wk

处理特殊项之后循环展开,读取内存次数会减少(对于 dpi1, 来说)。

去除分支

使用 __builtin_expect 函数。比如我们写了 if(A),不过 A 是一个几乎不会出现的 corner case,你可以写成 if(__builtin_expect(A,0)),同样定义几乎一定会发生的变量。

编译器会倾向于预测一个指令是否发生。如果不可预测,可能要采取手段。

cmov

据说这个更快。用 min 或 max,或者三目(提前计算好值),类似

int a=...,b=...;
if(a<b) a=b;

位运算

如果不可预测用 & 之类的代替 &&

使用 __builtin_ctz 遍历会提高枚举 mask 的速度(存疑,效力并不是太明显)。

只使用比较运算不用 if 不会产生分支。

用 long long 代替 pari 之类的东西

就是更快。原因是,long long 存储加载比两个 int 快,而且比较 long long 是没有分支结构的。

内存访问相关

  1. 内存局部性

让访问尽可能连续。比如预处理 st 表的时候,把小的那一维放在前面。区间 dp 的时候,可以按照 (l,k+,r+) 的顺序枚举。

  1. 时间局部性

让适合缓存的数据在一段时间内被反复访问。

比如滚动数组,如果只开一个(使用合适的转移顺序)会更快;要多次访问数组 a 上的一个区间,可以进行分块,每次加载一个块内的数据再跑更快。

  1. 内存并行

如果地址可以通过简单的算术得到(而不是指针),认为他们是可以并行的,这样更快(比如树状数组和非递归线段树)。

  1. 树的重标号

想起来 nfls 之前的案例:对树进行重标号之后,从无法通过变为最优解。

对于那种只和父亲有关的树上 DP,可以按照 BFS 序重标号;子树树链相关的,可以考虑 DFS 序重标号。

使用 DFS 序求 LCA 是高贵的、正确的、先进的,而且适合连续访问内存(比如你需要求出一个点到其他所有点的距离。)

STL 相关

  • vector 可以用 reserve 分配内存,减少 push_back 常数;

  • emplace_back 据说更快;

  • vector 的移动使用 A=move(B);

  • set 的 insert 会返回 (iterator,bool) 的二元组,可以利用一下;

  • erase 返回的是下一个数的迭代器,通过迭代器删元素是 O(1) 的;

  • insert(it,x) 如果 x 在正确的位置就是 O(1)(it 之前)。所以插入两个很接近的元素就可以用这个方法降低常数。初始化 set 可以排序然后在 end 之前插入。

同样适用于 map。

priority_queue 常数非常小,积极使用(代替 multiset);__gnu_pbds::gp_hash_tableunordered_map 牛,具体用法靠前看。

sort 用匿名函数更快。

计算密集型算法

计数题通用优化

计数题一堆取模对不对,少取几次模更快。

  1. 提公因式。
  2. 用加减代替取模。
  3. 无符号的东西更快。
  4. 使用较大类型数据进行暂时存储。但是可能存不下。比如是四个元素乘起来,你可以每 128 轮对 128P4 取模。注意你可以通过手法使得不出现取模只有加减。减少分支的手段:image
  5. 减少取模链。就是我特别喜欢写 a*b%MOD*c%MOD*d%MOD 这种东西。它不能并行,延迟很高,可以使用分治合并。超长的乘法链(比如阶乘)使用循环展开,并且减少依赖链。

内积优化

很多题目都是内积的形式,代替 FFT 骗分。乘法是少不了的,据说最优的是每 16 个分成一块,用 u64 暂存,加到一个 u128 里面去。

后面的明天写 /shui

posted @ 2025-06-09 23:44  M2GA  阅读(6)  评论(0)    收藏  举报
编辑推荐:
· 为什么PostgreSQL不自动缓存执行计划?
· 于是转身独立开发者
· C#.Net筑基-泛型T & 协变逆变
· dotnet 代码调试方法
· DbContext是如何识别出实体集合的
阅读排行:
· 【Cursor保姆级教程】零基础小白从安装到实战,手把手教你玩转AI编程神器!
· MySQL查询执行顺序:一张图看懂SQL是如何工作的
· Cursor 实战万字经验分享,与 AI 编码的深度思考
· 工作流引擎系统-基于橙单(flowable)的系统开发-流程配置举例
· 用 AI 制作超长视频,保姆级教程!
点击右上角即可分享
微信分享提示