总结
数据结构:
\(DS\)题不要依赖板子,要自己写,包括各种图论树论数论的题,考场没有板子给你看。
平衡树与线段树
平衡树/线段树五问:
1.每个节点需要记录那些信息
2.需要那些标记
3.下传标记怎么做
4.区间整体修改怎么搞
5.如何合并区间
线段树记得返回值啊。
如果有对于一个区间的操作,如果端点数较小,考虑离散完丢线段树上处理
\(Fhq_treap\)在维护名次树按\(v\)分裂,维护区间按子树大小。
树状数组
树状数组注意值域大小
尺取
对于一类\(max - min\)的题目,可以考虑按权值排序后尺取。
CDQ分治
cdq分治注意左子区间,右子区间,本区间的更新顺序,考虑是否左子区间对右子区间造成的影响是否会影响右子区间本身的更新,如最优化问题
堆
k个最值的求法,先对每个元素进行最操作,再对堆顶进行次操作丢入堆。
考虑堆实际上一个,排序完后依次插入树的一个结果。
启发式合并
对于数据结构的合并,大概是暴力合并每个点?
考虑把小的合并到大的来,进行一个启发式的合并
这样复杂度是\(O(n min(x,y)) = O(nlogn)\)
multiset:
multiset删除未出现的元素会出现问题。
树论:
树上路径:
看到树上路径,考虑拆成\(u->lca>v\)两段处理。
直径
树的直径的一些性质:两颗树由一条边连在一块,则新树的直径,定为原两颗树的直径的两个端点取二
就是找一个最远点,让\(dis(u,k)\)最长,在树上的话,我们知道这个点\(k\)是这颗树上直径的两端之一。
一些结论
区间移动,求rk,可以考虑在 \(n + m\) 的序列上操作,用log的数据结构求rk。
树上距离问题
考虑树上的关键点群的路径和可以通过用dfn排序,相邻距离和除2来求
字符串
子串
考虑数满足某个条件子串的数量,可以从结尾统计
tire
注意tire树的空间。
hash
静态相等问题可以用hash做。(比如判数组相等问题,可以对每个下标随机一个权值)
思路
两种对于字符串匹配有包含关系的结构:
AC自动机的fail树
后缀自动机的后缀link树
细节
strlen是O(n)
图论
基础
在图上遍历要写
for(int i = head[u];i;i = e[i].next)
差分约束系统:
在构造时要对构造出来的系统保持清醒认识。
在判负环时可以考虑牺牲一些正确性换取时间
园方树
圆方树记得不要退栈到\(u\)
tarjan
割点:
low[v] >= dfn[u] and !(u == 1 && son_size == 1)
强连通分量:
要记得维护在搜索树上的点,low,dfn只在他们中间更新。
dfn[u] == low[u],出栈到u为一个强连通分量。
点双:
low[v] == dfn[u]
退到v,加上u是一个点双
点双:在块内 ab 两点之间一定存在两条点不相交的路径
边双
low[u] == dfn[u]
退栈到u
边双中能够满足以下条件:
对于任意对点对,都可以调整无向边方向为有向,保证有\(s_i \to t_i\)都成立。
边双:在块内 ab 两点之间一定存在两条边不相交的路径
动态规划
优化
斜率优化一般不用设初值,但如果有多层dp,建议跑第一层dp初值,单调队列\(head < end\),只要觉得自己柿子没推错,就相信自己。
状压
等边权无向图其最短路树为BFS树
子集dp
枚举子集:
for(int S1 = S;S1 != 0;S1 = (S1 - 1) & S) del(S1);
细节
控制转移次数的最小可以每次减去一个INF
数论:
基础
\(\sum (n/i) = nlogn\)有点蠢
容斥
考虑容斥,考虑一个和式求解的柿子可以考虑插板法。
矩阵乘法
对于矩阵乘法,多次询问,单次是倍增做法的,可以考虑记录一下每个倍增点的矩阵,少个n。
鸽笼原理
考虑到有\(m\)个限制,可以考虑\(m+1\)个最大的答案,这样可以利用鸽笼原理得到答案。
计数
方案数的次方,可以看做多个单独操作的合并
取模问题
\(a^{x1^{x2^{x3....}}}\pmod{p}\),可以在\(log(p)\)次的暴力用扩展欧拉定理解决。
一些结论
最小权覆盖集 = 全集 - 最大权独立集
正难则反。
Q:有\(k\)个点要统计所有点对的一种值。
怎么怎么在logc次分组内遍历所有点对。
A:对二进制下每一位都按0,1分组。([GXOI/GZOI2019]旅行者)
每个元素有0/1状态,翻转状态看成异或。
Dilworth定理:最长反链=最小链覆盖=最大独立集
区间问题
考虑\(|a - b|\)转变成\((a,b)\)区间长度
数个区间求最长交集,考虑按\(l\)排序,维护最大的\(r\),考虑答案为\(max(min(r,r_i) - l_i)\)
贪心
一些结论
有多个相同对象操作求最值考虑差分并贪心。
二分
考虑比较关系的,可以考虑二分一个中间,把他转换成01串来操作。
杂项
思路:
碰到类似于\(2^n\)的东西往二进制上靠
遇到类似于一个点对一段区间有贡献的题目,往树上想。
字典序最大的一类题目:
正解则是考虑按位确定:考虑从 \(1\) 到 \(n\) 依次确定该点选了哪个值,确定 i 这个位置的值时,我们选取满足以下条件的值:
- 使得这个东西确定之后,存在一种 \(i+1\) 到 \(n\) 的分配方案,使得总方案合法。
- 尽可能大。
考虑抽象模型
如果两个函数一个单调增,一个单调减,如果两个函数相交是理论最大值。
但是由于两个函数是离散的,可以考虑这样一个东西。
那么答案是如下两个\(k\)的最大值:
\(f_1(max(k)),f_1(k) < f_2(k)\)
\(f_1(min(k)),f_1(k) >= f_2(k)\)
由于函数性质,所以可以进行二分。
看到区间形式,考虑是否能够进行前缀消除转化为[1,l - 1],[1,r]的操作,如异或粽子
对于一些无法正面求解的问题或者条件,考虑正难则反。
或者先对一些易满足的条件进行满足,在这个基础上进行调整。
对于\(n^3\)复杂度的东西,矩阵,区间dp考虑一下。
最大前缀和等价于:
前缀的后缀没有小于0的
后缀的前缀没有大于0的
解决最优化问题的一类方法:
网络流,费用流
贪心
动态规划
做一个不满足题目约束条件下的解,并考虑调整他。
LIS和LDS的长度一定有一个大于等于根号n来着吧。
细节:
注意数据范围,\(long long\)是否要开,数组是否够大
快读时如果保证没有负数可以不写判负数的程序,能快不少。
注意哪些地方要\(ll\),哪些地方不用,\(int\)和\(ll\)速度差很多,常数差就在这里。
low[u] == std::min(low[u],dfn[v]);
这个是不会报错的(要写单等号)
不要写错东西,思路要清晰,过了样例并不能说明什么,一定要记得对拍
要敢于写代码,不敢做就永远跨不出那一步,你明明能做的很好。
数组A[N],下标只到N - 1,别被RE送回家
对于某些操作使答案变化的题,如果没法直接维护,可以思考是否有一些性质的操作能让答案变得更好。
指针移动完可以考虑再向两边扩展一下保证正确率,甚至可以让假做法过去。在明白有单调性但不是很清楚时可以用。
指针移动要考虑移动的范围,不要出界。
数据量大就记得快读,最好养成大于\(1e5\)就快读的习惯。
取模最好手动实现,因为直接取模真的很慢。
在函数里定义变量记得赋值。不然会出现随机的数据
众所周知sqrtt是一个跑得极慢的函数
所以我们可以两边平方消掉sqrt,最后再算一次即可
就是如果一个东西有很多操作会贡献,然后一个东西只贡献一次, 我们强制他贡献的时候只在第一个把他贡献的位置计算。
自信