常数小技巧
- 不论整数,浮点数,除法是真的慢
- 整数取模尽量避免,这也是大部分程序的卡常关键(比如20200610的T1,pcf就被两次取模卡掉了40分)。
- 要尽可能让几次对一个数组的调用地址距离近。比如(f_{i,j,k})中,大部分运算是在相同的(i,k)下做的,那么我们应该存为f[i][k][j]。(upd20201029:这个东西真的还挺有用的,APIO2013机器人就是拿这个过的)(一个更好的例子:当我们定义ST表的时候,大部分时候我们应该定义ST[21][MAXN+1],实测很有效果)。
- 一个优秀的快读快输很有必要(但是事实上很多人的快读快输是错的,不能读-2147483648)
template<typename T> void Read(T &cn)
{
char c; int sig = 1;
while(!isdigit(c = getchar())) if(c == '-') sig = 0;
if(sig) {cn = c-48; while(isdigit(c = getchar())) cn = cn*10+c-48; }
else {cn = 48-c; while(isdigit(c = getchar())) cn = cn*10+48-c; }
}
template<typename T> void Write(T cn)
{
int wei = 0; T cm = 0; int cx = cn%10; cn/=10;
if(cn < 0 || cx < 0) {putchar('-'); cn = 0-cn; cx = 0-cx; }
while(cn)cm = cm*10+cn%10,cn/=10,wei++;
while(wei--)putchar(cm%10+48),cm/=10;
putchar(cx+48);
}
- 这里是一个我在某次训练里手打的fread,fwrite的模板,搭配上面的快读快输使用,感觉还挺好的。(程序开始的时候需要调用pre_in(),pre_out();结束时需要调用end_out();)
#define MAXNUM 1000000
char tmp_in[MAXNUM], tmp_out[MAXNUM];
int tmplen_in, tmplen_out;
void pre_in() {tmplen_in = MAXNUM; }
int getone() {if(tmplen_in == MAXNUM) fread(tmp_in,1,MAXNUM,stdin), tmplen_in = 0; return tmp_in[tmplen_in++]; }
void pre_out() {tmplen_out = 0; }
void end_out() {fwrite(tmp_out,1,tmplen_out,stdout); }
void putone(char cn) {if(tmplen_out == MAXNUM) fwrite(tmp_out,1,MAXNUM,stdout), tmplen_out = 0; tmp_out[tmplen_out++] = cn; }
- 一个优秀的快速幂也比较有用
LL ksm(LL cn, LL cm) {LL ans = 1; while(cm) ans = 1ll*ans*(1+(cn-1)*(cm&1))%MOD, cn = cn*cn%MOD, cm = cm>>=1; return ans; }
- 快速乘的正解是__int128
- 循环展开
- 循环展开是最后的计策
- 循环展开的本质是缓存优化。可以参考APIO2019的课件。所以如果展开4层还没有什么效果,那基本是没救了。(除了矩乘)
- 一份比较好的示例代码
LL ans1 = 0, ans2 = 0;
int i = 1;
for(;i+3<=n;i+=4) {ans1 = ans1+(a[i]+a[i+1]); ans2 = ans2+(a[i+2]+a[i+3]); }
for(;i<=n;i++) ans1 = ans1+a[i];
ans1 = (ans1+ans2)%MOD;
- memset,最好算好要用前多少位,然后精确清零
- 正确分析好程序的复杂度瓶颈,然后只在瓶颈上优化,除非其它地方优化起来太方便了,可以顺手搞一下。毕竟卡常的时候是很容易fst的。
- memcpy也挺好用的。memcpy(destiny, source, sizeof(unit)*length)。
- 分块这种东西啊,T与不T常常就在常数之间。能用一些分块的数据结构之类的,就少用整数分块(活生生的例子:20201016_T2)。
- 在大规模处理的时候,少调用函数(比如你要回答1e7组询问,那还不赶快手动inline)。(例子:20201130_T3)
数组
- (2^{20})开1e5,1e6都不够。
- 数组一定要科学清空
- 数据千万条,清零第一条;多测不清空,爆零两行泪。
- 分块内部开哈希,修改的时候一定要全面修改。
- 数据范围要好好注意一下,并不是每一道题的(n)和(q)都是相同的。
- 经典节目:数组开小(少加一,或者少开两倍)
- 要注意数据范围看对:有时候最大的数据范围会在数据范围那里的第一行,而不是最后一行————最后一行有可能是倒数第二个Subtask。
- 很多时候主席树开40MAXN没有问题,甚至可以说比较安全。但是我发现这还是有爆空间的风险。(我有一天的T1就是因为空间限制128MB,n大概是2e5,主席树一个节点里记录了四个元素,开了40MAXN,然后就光荣爆炸了)
其他细节
- 爆int和爆long long是真的自闭。我今天调题,有一大半的时间都是因为爆了int而自闭。+1+1
- 特别要注意的是计算几何和min25筛。计算几何的问题主要是存在叉积,而min25筛的问题是n都会爆int,直接n*n就爆long long了。
- 名称相似而且作用相似的数组,一定要第一遍写对,并在静态查错的时候重点关注。最好名字要容易区分一点。
- 未定义行为检测真有用。-fsanitize=undefined
- 边界情况好好判啊。写完之后哪怕多花五分钟搞点边界数据卡一卡也好。
- 好好判无解之类的,千万不要默认他永远有解。
- 数据分治,如果要直接在主程序里return Sub1::main(); 那么Sub1::main()的返回值一定要是0。可以用未定义行为检测(-fsanitize=undefined)/-Wall/windows环境里的dev-c++来避免。
- 修改边权点权的时候,不仅要在维护的数据结构中改,还必须改一下原始数组。
- 读入优化好是好,就是判到EOF的时候容易出事。
语法碎碎念
- c++中,class和struct的效率差不多。事实上struct的实现就是所有元素都是public的class。class的功能更强大,相应地,struct的运用更方便。用class还是struct完全是个人习惯问题。
- 我觉得struct和class的并存的原因,一个是兼容标准问题,一个是用法习惯问题。如果是一个五元组,显然用struct更符合“习惯”;而如果是一个功能比较复杂的东西,比如lct,或许使用class会更顺眼。
算法碎碎念
杂项
- 数论分块真香!
- Update 2020.06.11:他太蠢了,我昨天的T2,加个数论分块就wa了,一切都是因为我没有把一个每次都乘的数字做幂。但事实上那个数论分块不加这道题也能过。
- 模块化真香!
- 斜率优化判交点,如果直接用一些解析几何或者计算几何的方法(((k_1-k_2)(b_3-b_2)<(k_2-k_3)(b_2-b_1))这种),很有可能会爆精度、爆long long,然后让你调到自闭。
- 分块真香。如果每一块中的操作极其雷同,甚至可以前缀和。
计数
- 可以一上来就莽式子,反正都是一通爆乘一通爆加,写就完事儿了。
- 考虑用dp来计数。
- 但是千万不能忘了容斥这个工具。
- 反演也挺香的。特别是二项式反演(有关选择,但是其中的组合数学部分要慎重考虑)和莫比乌斯反演(有关gcd,约数)。
- 很多时候不能一下子全都推完。这时候就要一步一步慢慢来,先用暴力写出来,看看这个式子到目前为止对不对。
- 普通的题目是推式子推到最后才要考虑套上多项式的。当然也有一上来就莽的。比如仙人掌计数。
- 如果要维护形式幂级数,那么也可以维护点值,最后插值插回去。
- 有些东西写出式子来不一定能看出怎么优化。写到程序里能更清晰一点。
贪心构造
- 第一种思路:发现自己如果知道一段是有解的,可以用题目能接受的复杂度求出来。那就继续深刻理解判断有解的条件。尝试简化/形式化/用推理弱化。
- 第二种思路:发现自己如果知道一段是有解的,可以用题目能接受的复杂度求出来。考虑优化求解的方式,用这个方式来判断一个区间有无解。
关于网络流
- 建图:
- 如果是网格图,黑白染色是一个非常好的思路。
- 如果一个人要用两种东西,那么把一种东西放在前面,一种放在后面跑费用流也是不错的。
- 费用流
- 我们通常跑的都是最小费用最大流。这时我们通常是要求流满的情况下费用尽可能小。但是有可能会有这种情况:我们出现了负权,每次流只是为了赚钱。我们也不一定要流满。此时就应该每次流都判断一下,如果开始亏钱就退出。参见20200929T2。
关于数据结构
- 我人傻了。我维护区间加区间乘的lct,竟然在pdown的时候搞反了乘和加的顺序(我一般都是先乘再加,因为这样处理tag的时候比较自然,但是我pdown的时候竟然先加再乘了)。
- KD-Tree的更新要很注意,不要随便更新0(当然也可以对0进行特殊处理使得更新他没用,比如一个区间最小值,0上设为INF。)其他数据结构同理。
数字收集
ntt模数(少见)
104857601 = (25 imes 2^{22}+1) primitive root = 3 max ntt = 4194304
2025848833 = (483 imes 2^{22}+1) primitive root = 10 max ntt = 4194304
377487361 = (45 imes 2^{23}+1) primitive root = 7 max ntt = 8388608
2088763393 = (249 imes 2^{23}+1) primitive root = 5 max ntt = 8388608
754974721 = (45 imes 2^{24}+1) primitive root = 11 max ntt = 16777216
2130706433 = (127 imes 2^{24}+1) primitive root = 3 max ntt = 16777216
167772161 = (5 imes 2^{25}+1) primitive root = 3 max ntt = 33554432
2113929217 = (63 imes 2^{25}+1) primitive root = 5 max ntt = 33554432
469762049 = (7 imes 2^{26}+1) primitive root = 3 max ntt = 67108864
1811939329 = (27 imes 2^{26}+1) primitive root = 13 max ntt = 67108864
2013265921 = (15 imes 2^{27}+1) primitive root = 31 max ntt = 134217728