OI 中一些可能有用的小 Trick 与注意点

不要有拖延症,它的危害在于会让你犹豫,如果有要去做的事情,倒数五个数后就去做。
相信直觉,直觉有时只是思考过快的产物。
做题别急,要多想,当你想到一个做法时不要随意否定然后抛,要多想。
注意大部分优化都是建立在优化重复/多余贡献上,注意发现问题中的重复/多余贡献。
打部分分的时候一定要检查是否判断正确。看到区间+-x一定记得差分等。
要学会复杂度证明,有时会有大用。厕所是OIer灵感之泉。注意打表找规律的应用。
一定要注意时空限制,数据范围,输入格式等每一处信息,不要漏掉信息。
比赛初始一定要先通览所有题目再去思考/做。set效率<堆+惰性删除。记得看数据范围,有无负数之类的。
注意 dp 等上界究竟可以取到多少,是否只能取到某个更小的值,有时一点看似很小的优化可以优化掉一个维度。
遇事不决先考虑 dp,数据结构等。遇题考虑矩阵快速幂或线段树维护矩阵或矩阵转移dp等。
学会考虑组合意义优化。邻接矩阵或置换可以考虑快速幂,邻接矩阵快速幂后的意义是走恰好k步从s到t的方案数。
看到重复合并没有多余贡献时一定考虑ST表。做题时考虑代数基本定理和(扩展)欧拉定理,单位根反演,线性规划等.
注意期望和方案数的互相转化,不要觉得方案数转成期望一定更复杂。曼哈顿距离考虑分离x和y轴分开算。
dp时如果转移困难,考虑容斥原理,加状态,bitset等。思考格路计数优化(dp等)
一定要检查数组空间,即使你已经检查过了,很多 WA 的问题实际上都是数据越界导致的。
看题解前一定要经过自己的仔细思考,思考自己想到了哪一步,下一步是怎么接上的,学会自己憋题,集中注意力,平常模拟赛不要过于注重成绩,可以使劲憋题。
set常数是真的大,1e5 nlogn 都会T掉,一定尽量不要用。打代码时分清楚 ===,注意 <=< 等边界情况。
\(\sum k\) 有限制的的时候一定要加一些看似无用的复杂度 n 优化到 k 的优化,如虚树,dp关键点转移,关键点贡献,关键点转移等。区间问题一定考虑前缀和/差分,不要上来就线段树。给别"人"讲题会加深你的理解(费曼学习法+小黄鸭调试/学习法)。
莫队等数据结构的复杂度保证在于块长,如果一个点有多个数而不展开处理的话有时可以构造数据卡掉,复杂度退化至平方等。遇见题目一定要考虑组合意义或打表,即使你觉得它不可做。做题时一定考虑 bitset 优化。考虑正难则反等。
dp等时一定要注意当前的具体上界最多到哪里,或预处理优化,注意多种小情况特判。
看到某些要求选出的点 \(\sum a_i \le k\) 时考虑二分一个 \(k'\) 然后对所有 \(a_i\le k'\) 的点做取舍。
数组开小或不开longlong或爆空间RE可以返回任何错误,做题时优先考虑这一点。
矩阵题不一定要矩阵,可能只是一个图论,一个点或一行一列是一个点,考虑网络流等。
检查数组内存,记住多测清空和long long。充分判断是否不应开long long。
做题要充分发挥人类智慧,记得写对拍。和式求东西注意拆项/化简/转递归式。
多考虑抽屉构造。判断全部相等<=>最大值=最小值。考虑dfs树,tarjan等。
递推时可以考虑两个数的差或异或之类有关系使得所有数只由第一个或几个元素确定。
要注意tarjan是反的拓扑序(如2-sat要取拓扑序靠后的在tarjan上要取col前的)
矩阵乘法+dp+线段树有奇效。考虑最长上升子序列/最大上升子序列和 离散化后使用值域树状数组。
要仔细读模数和输出大小写
先问是不是,再问为什么
注意事项
tarjan坑点
考虑逆向思考,比如正难则反等,当删边不好弄时就考虑转换成加边之类的。
遇见一个点状态会转变时考虑拆点建分层图。
inline? resigter? ll or int? size? min max?
要考虑把状态和存储的答案交换,要判边界
树的连通块个数是点减边数
不要随意hack掉自己的代码,要切实颅内跑一遍,不要随便抛做法。
一定要造一道极限数据(max and min)自己测一下。
如果你调不出来题可以考虑检查若智的bug,甚至对拍,不到万不得已不要重构。
要考虑多种情况或边界。
遇到递推式访问到后面的式子时不妨移项使得只访问前面的式子,
或者说也可以是下标同时减一加一。
\(p_i=ap_{i-1}+bp_i+cp_{i+1}\) 可以移项得到 \(p_{i+1}=\frac{(1-b)p_i-ap_{i-1}}{c}\)
学会考虑容斥,想想哪一部分出现的次数/概率一样。
比如计算 \(\sum_{i=1}^n \sum_{j=i+1}^n a_ia_j\)
可以变成 \(\frac{\sum_{i=1}^n\sum_{j=1}^na_ia_j-\sum_{i=1}^na_i^2}{2}\)
然后变成 \(\frac{(\sum_{i=1}^na_i)^2-\sum_{i=1}^na_i^2}{2}\)
判断集合是否相同考虑随机赋值做xor hash/sum hash。
观察到答案和数组内部顺序无关,即所给数列可看做可重集时,要向两个方向思考:
1.将数列排序。
2.对数列求出权值数组,在权值数组上考虑问题。
考虑能否离散化,排序,异或哈希,贪心,线段树,dp等
记得测试极限数据,不管极限大还是极限小,记得打对拍(所以要先打暴力)
不要打错正负号,不要推错式子,多考虑是否能取等,边界条件和特殊情况例如n=1
注意代码里的>=<=+-正负号和是否取等
做题时多想想dp,矩阵乘法,线段树,快速幂等,有时考虑推式子。
考虑根号分治和线段树,看到mex等可以+1-1维护的考虑莫队。
线段树要注意多个标记的更新顺序,做题一定要写对拍。
取模不要写错加减<= <等符号,取模不要写错加减<= <等符号,线段树记得区分线段树编号(now)和当前原编号(l)。
实在调不出来考虑先看讨论版再发帖。线段树不要忘记build,注意多测清空和开long long。
看题看清连续子序列还是子序列。
当遇到gcd或数论时可以在素数上考虑,比如我们由代数基本定理设 \(a_i=\prod_{i=j}^kp_j^{c_{i,j}}\) 那么 \(\gcd(a1,a2,...)=\prod_{i=1}^kp_i^{\min_{j=1}^n c_{j,i}}\)
注意遇到某些题目可以考虑正反图上两边dij。
注意遇到某些题目可以枚举一个去找另一个。
注意遇到题后记得在通用形式上思考。
与得到1和或得到0的限制更严,需要考虑。
区间加等差数列可以维护两个标记(不超空间)/差分数组上考虑(仅限单点)/维护数列 \(i\times a_i\)(不超值域 or 取模(预处理逆元))。
两条路径(a,b),(c,d)相交当且仅当lca(a,b)在(c,d)上,或lca(c,d)在(a,b)上。
判断某个点是否在某条路径上,要满足该点到路径两端点的距离和等于两端点的距离,或者lca(a,c)=c并且dep(c)<=dep(lca(a,b)),并且lca(a,c)=c并且dep(c)<=dep(lca(a,b)),实际上满足一个另一个也自动满足了。
如何判断括号序可以看是否满足递归的定义,前缀和以及栈内清空:
1.空串是括号序。
2.若A是括号序,则(A)也是。
3.若A,B是括号序,则AB也是。
括号序
看到可以有误差 + 庞大的数据范围,一眼看比值/打表。
对带编号的数论问题,把编号改成由 0 起始/由 1 起始/二进制/dfn序 等等对结果可能有意想不到的惊喜。
看到子树问题考虑 dp,启发式合并,dfn序,树剖等。
支持 min-max 容斥
强者的博客
适当加入取模优化。
总和为零可以应用在栈内为空,例如可以套区间用前缀和(第7条)求子串是括号序的数量。
看到可以消除的题一定要想到在栈内做,不仅好写也好想。
遇见区间时不要只想线段树什么的,也要先考虑前缀和或者差分。
注意记忆化或者离线的使用,做题手模样例,考虑二分,检查文件。
做题时想到一个做法一定要正确仔细的算复杂度,不要看一眼就以为会超时直接抛。
0.一定要注意线段树时max和min如果重定义函数会被调用两次使得O(logn)->O(n)。
还有set自带lower_bound是O(logn)的,STL的在set上O(n)。、
考虑 bitset 或指令集优化。
记得问题转化。
判集合是否相同的,考虑直接随机赋值然后做xor hash/sum hash。
一定要学会推和式式子
学会缩链。
学会数形结合。
一定要多推式子,看好赋值顺序。
考虑悬线法
学会思考用堆实现反悔贪心,或者二分+贪心,不要只想纯贪心。
无论是 dp 还是推和式还是什么,都一定要记得拆出某一项思考。
如果两个东西可以转化考虑mod3比如设x=1,y=2则在mod3意义下 \(x+x=y\),\(y+y=x\),\(x^{-1}=x\),\(y^{-1}=y\)
看到图论题先想一下这几个问题:图是否连通?有向图还是无向图?是否带权?有没有重边与自环?
遇到矩阵问题不要老是想和矩阵有关的算法,有些时候或许只是道简单的图论。可能每个点是一个点,也可能一列一行是一个点,也有可能是每个网格是一个点,然后边权是被割断的边的边权(交通规划),都有可能。
看清楚是有向图还是无向图,无向图空间要开 2 倍!网络流的边数与点数不能算错!
1.考试的时候先考虑dp线段树,当多次 \(dp\) 会超时时,考虑写转移矩阵。
2.记得检查数组空间。
3.考虑尽量卡常。
4.尽量考虑推式子。
5.看到关于01爆搜选择的一定要先考虑01背包,不要直接写爆搜。
6.清楚要不要文件读写和子文件夹。
7.遇见区间时不要只想线段树什么的,也要先考虑前缀和或差分。
8.看清楚是有向图还是无向图,无向图空间要开 2 倍。
对于看上去没啥性质的函数应该观察其结构,找到一些结构上的性质,使得我们能用某种结构去维护它。
对于看上去不能做的一坨式子考虑推式子然后数据结构维护。
9.在缩点或拆点时分清旧图和新图。
数据结构题不要只想 log 结构,考虑分块,根号分治,莫队。
有些 DS 题会问你一个区间 \([l,r]\) 内需要划分成至少几个子序列以满足题意,比如这道题,这个时候有一种思路就是先双指针处理出每个点往右边能够预处理的最右点,然后倍增即可。
FHQ Treap 在处理区间信息的时候如果需要维护懒标记的时候需要注意 Merge() 函数中 x,y 都需要下传懒标记。典型例题。
无论是什么 ds 题,一定要将想法往可合并性上靠,或者莫队/分块。
如果出现区间取模/取对数/开根号之类的玩意,可以考虑这玩意取模会砍半/取对数与开根号数字降的很快等从而可以暴力做并且复杂度正确,并且该情况下单点修改操作也允许并且复杂度也是对的。
10.如果问能否成立波峰波谷就排序后按照 \(a_1,a_3,a_5,...,a_n,a_{n-1}...,a_2\) 的顺序构造然后判断。
11.如果考虑数组每个数能+k-k求最小极差

sort(a+1,a+n+1);
int ans=a[n]-a[1];
for(int i=2;i<n;i++) ans=min(ans,max(a[n]-k,a[i-1]+k)-min(a[1]+k,a[i]-k));

12.考试时注意是否多测,多测清空,输出的YESNO大写小写,看看有没有保存,能不能过编译,用cph是最好的。
13.打线段树主席树等大空间ds一定要多测大数据防止数组开小RE,注意long long的使用,滥用可能会造成超时或超空间,不用可能会溢出,建议随手#define int long long+检查会不会超时/超空间。
14.调试时代码顺一遍,思路顺一遍,然后找几组小数据自己手模一下代码,对拍,然后瞎调。
15.一定要注意warning,不要只注意error。
16.树上边权转点权转移到儿子节点,但是特别注意多余信息处理(尤其是树剖的时候)
比如树剖结束的时候处理最后一条重链,最顶端节点的答案不能处理进去(因为会有多余信息(\(top_x,fa_{top_x}\)))还有一些计数题比如说统计路径上有多少个不同颜色的,注意一下根节点的多余数据处理(因为根节点是没有颜色的)。
遇到类似于 n 个东西分成两组,差最小等问题,不要总是想着一些奇奇怪怪的算法,如果就是个容量为 \(\frac{n}{2}\) 的背包问题呢? 此时的体积和价值都是这 n 个东西的属性。

遇到区间问题多想想差分,尤其是树上差分。
类根号分治思想真的是非常常见又好用的一种思想!
看到题目中有回溯操作时,除了可持久化外也可以想想建立操作树。
当你想不出题目的时候不妨考虑一下随机化乱搞比如说模拟退火或者是随机权值和 hash/xor hash。

对于一张无向图的邻接矩阵而言,如果 \(A_{i,j}\) 表示 \(i\)\(j\) 之间是否有连边,那么做矩阵快速幂 \(A^k\) 之后,\(A_{i,j}\)的值表示从 i 出发走 k 步有多少方案到 j。
位运算三大处理方法:线性基(异或专属),拆位,01Trie。推式子时可能 a⊕b=a|b-a&b,a⊕b=a+b-2(a&b) 会有用。
拆位处理二进制时,注意或得到 0 和与得到 1 的限制是更严的,做题先考虑这一点。
枚举 \(S\) 及其子集,\(O(3^n)\)
for(int r=s;r;r=(r-1)&s)
复杂度证明考虑枚举子集 ,然后写出式子二项式定理化简。
注意有 n 和 m 的题要小心 n 和 m 写反。
注意数组不要开小或开大。
注意模数是不是质数。
注意操作的顺序。
认真读题,模拟完样例再写程序。
注意清空数组。
相信所有题都是可做的。
dp 优化:凸优化(wqs)、斜率优化,决策单调性、交换状态和值域、减少状态(包含常数上的)。
vector 比 map 存分段函数常数小。
vector 建图特别慢,换成链式前向星会快很多,尽力减少状态和转移数量。
卡询问次数时可以考虑 dp 求出最优询问次数,而非直接构造策略。
-fsanitize=undefined 在函数参数为 int 但传入一个大于 int 范围时不会报错。可以使用 -Wconversion 并查看编译警告。
任何 n 较大的,可以快速算单项的东西考虑分段打表。
子区间问题有时候可以类似最大子段和的使用线段树维护,也可能分治。

posted @ 2023-09-28 23:23  liaiyang  阅读(103)  评论(1编辑  收藏  举报