学OI一年了,到现在联赛所需要的知识已经基本学完了。现在,有必要回过头来,总结总结自己一年来学到的知识以及得到的经验教训。
基础
-
语言基础
C++的语言基础啥的就略了吧。
-
算法复杂度分析
O:复杂度的上限。
Ω:复杂度的下限。
Θ:复杂度的上限与下限。
STL与<algorithm>
-
STL
http://www.cplusplus.com/reference/stl
全称Standard Template Library(标准模板库)。
vector:动态数组。
list:双向链表。
set:集合,其实就是红黑树,支持插入、删除、查询、最大最小值、前驱和后继。
map:也是红黑树,映射。
multiset:可重集。
multimap:可重映射。
stack:栈。
queue:队列。
deque:双端队列。
priority_queue:优先队列(虽然一般都当作堆来使)。
-
<algorithm>
http://www.cplusplus.com/reference/algorithm
算法库,包含了很多很实用的东西。
sort():神器不解释。
stable_sort():稳定排序。
swap():交换两个变量(懒人专用)。
min():较小值(可以传比较函数)。
max():较大值(可以传比较函数)。
min_element():区间最小值(其实是暴力)。
max_element():区间最大值。
make_heap():线性时间建二叉堆(可以传比较函数)。
push_heap():二叉堆插入后的调整,可以传比较函数(默认大根堆)。
pop_heap():二叉堆删除最大值时的调整,同样可以传比较函数。
reverse():暴力翻转区间,懒人专用。
upper_bound():二分查找(可以传比较函数)。
lower_bound():同上。
next_permutation():下一个排列(可以传比较函数)。
prev_permutation():上一个排列。
排序
-
STL sort
参见一.3.b。
其实就是快速排序的优化版。
-
快速排序
分治法,每次把当前区间划分成两部分,左半小于中间数,右边大于中间数,递归排序左右两半,最后无需合并。
平均复杂度O(nlogn),要规避O(n2)的最坏情况要使用随机化选取中间数。
-
归并排序
也是分治,把当前区间划分为相等的两半,分别排序,最后进行合并。
复杂度O(nlogn)。
-
堆排序
通过在当前数组中维护一个堆来排序。
复杂度O(nlogn)。
-
计数排序
记录每个数出现的次数,最后逐个输出。
复杂度O(n)。
-
桶排序
把值在一定区间内的数扔到对应的桶中,最后逐个输出。
平均O(n),优化后最坏O(nlogn)。
-
基数排序
从低到高对数据按位排序。
复杂度O(n)。
-
贪心法
就是每次选取最优决策,但是只考虑当前一步。
速度很快,但目光短浅,错误的贪心会导致WA。
分治法
-
基本介绍
把当前问题划分为几个规模较小的子问题,递归求解,最后将子问题的解合并得到整个解。
-
实例
快速排序,归并排序。
Strassen算法。
陈丹琦分治。
-
主方法
对于递归式T(n)=aT(n/b)+Θ(nc),其解有三种情况:
1.logba>c: 。
例:二分查找
T(n)=T(n/2)+Θ(1)
解为T(n)=Θ(logn)。
2.logba<c:T(n)=Θ(nc)。
例:快速选择(虽然并不太使用不过理想情况是这样的)
T(n)=T(n/2)+Θ(n)
解为T(n)=Θ(n)。
3.logba=c:T(n)=Θ(nclogba)。
例:归并排序
T(n)=2T(n/2)+Θ(n)
解为T(n)=Θ(nlogn)。
-
在线与离线
在线即顺序处理所有操作,每一个操作的进行不依赖于以后的操作。
离线需读入所有操作后按某种不同的顺序进行操作。
某些题目可能会通过强制求出上一个答案后才能进行下一个操作从而强制在线,这时就不能使用离线算法。
-
有根树
树的遍历
-
先序遍历
先访问根,再访问其子树。
-
中序遍历
仅限二叉树,先访问左子树,再访问根,最后访问右子树。
-
后序遍历
先访问子树,再访问根。
-
线段树
线段树的每个节点中均储存一个区间和其附加信息(和,最值等)。
-
普通线段树
支持四种操作:
区间查询:自顶向下递归,如果查询区间包含了当前区间则直接返回,否则递归左右两半。
单点修改:自顶向下把途径的所有节点的附加信息进行修改即可。
区间增量、区间修改:
这两种操作都需要用到延迟标记,在修改和查询到某个节点时如果要继续递归则下传标记并更新附加信息。
例题不再举例。
-
非递归式线段树
又称zkw线段树。
各种操作均采用自底向上的非递归写法,常数比普通线段树减小很多,接近树状数组。
zkw的作用就是卡卡常,没什么真功夫,不学也罢。
-
动态开点线段树
对于值为0(或者某个初始值)的节点,不再建出该节点,而是用一个哨兵替代,在更新为其他值的时候再创建该节点并往下递归(如果需要的话)。
主要作用就是节省内存。
例题:
2387
http://cojs.tk/cogs/problem/problem.php?pid=2387
对于每个数建一个动态开点线段树(实现时用map套),大概80行,比std的250行AVL树代码短很多。
-
可持久化线段树
又名主席树。
可持久化就是在从过去版本做微小改动得到新版本的过程中,不再复制过去版本并修改得到新版本,而是只建出有改动的点,其余节点指针指向过去版本对应节点。
例题:
找第k小的数
http://cojs.tk/cogs/problem/problem.php?pid=930
对每个位置建前缀和主席树,查询时把区间端点对应的两棵树一减就行。
-
树状数组
又名Fenwick树,巧妙地利用了二进制,使得单次操作所需时间均为logn。
-
单点修改,区间求和
单点修改直接向上更新即可。
区间求和直接两端点分别向下求和最后一减即可。
-
区间修改,单点查询
运用了类似差分的思想。
区间修改时左端点向上更新,右端点向上反更新即可。
单点查询直接向下查询变动值即可。
-
二叉搜索树
简称BST。
BST的基本性质不再阐述。
-
Treap
通过记录优先度并保持优先度的堆性质保持平衡,实质是随机构建BST。
注意Treap的删除采用旋转到只有一个子树或没有子树再删除的写法,与普通BST不同。
-
SBT
由陈启峰发明的一种平衡BST,通过记录子树大小size保持平衡,满足每个节点的size均不于兄弟子树的size。
据说比红黑树还快。
-
AVL树
最早发明的平衡树,借助高度保持平衡。
每个节点的左右子树高度最多相差1。
经典数据结构。
啥都好,就是代码有点长……
-
红黑树
通过奇技淫巧(划掉)维护颜色来维护平衡,保证没有一条路径比其他路径长一倍,因此是近似平衡的。
别妄想了插入5种删除6种代码长得要命……这玩意儿除了用来做STL就别拿去考试了。
-
Splay
最重要的就是splay操作,用一系列旋转把指定节点转到根节点。
splay除一般BST的功能外还能进行区间处理,方法就是记录pos,在处理区间[l,r]时把l-1转到根,r+1转到根的右儿子,则r+1的左子树就是对应的区间,再应用延迟标记可以实现线段树的所有功能,并且还能实现区间翻转、查询名次等线段树不具有的功能。
Splay的单次操作可能比较慢(最坏可以达到O(n)),但均摊效率是O(logn)的。
注意,Splay的常数很大(甚至比线段树还大),谨慎使用。
几种BST的比较
名称 |
平衡依据 |
平衡性 |
效率 |
难度 |
实用度 |
备注 |
裸BST |
无 |
C |
C |
C |
C |
不明觉厉 |
Treap |
优先级 |
B |
B |
B- |
A+ |
简单易学 |
SBT |
子树大小 |
A+ |
A+ |
B |
A |
短小精悍 |
AVL树 |
高度 |
A |
A |
B+ |
B |
绝对经典 |
红黑树 |
颜色 |
A |
A+ |
A |
C+ |
效率极佳 |
Splay |
伸展 |
B |
B+ |
A- |
A |
功能强大 |
-
并查集
每个节点均只存储一个prt指针指向父亲节点(初始时指向自己),也可能记录附加信息。
单次操作复杂度均摊O(1),总复杂度一般用O(mα(n))表示,这里α(n)是一个增长非常慢的函数,算法导论中证明了对于所有实际应用α(n)均不大于4(使得α(n))=5的n值比宇宙中的原子数目还多)。
基本操作
-
查询与路径压缩
通过把查找路径上的点的prt全部指向树根从而减少下一次查找所需时间。
路径压缩的时候可能需要维护附加信息,如食物链,银河英雄传说。
-
合并
记要合并的节点为x和y,则执行以下操作即可:
prt[findroot(x)]=prt[findroot(y)]
也可以使用启发式合并,不过一般这样就足够了。
当然也可能需要维护附加信息,比如集合大小。
-
附加信息
子树大小,如侦查circle。
其他附加信息,如食物链,银河英雄传说。
应用
-
Kruskal算法
用于维护集合及两个元素是否在同一集合中。
不需记录附加信息。
-
Tarjan LCA
类似。
-
例题
亲戚
http://cojs.tk/cogs/problem/problem.php?pid=259
裸的并查集,没啥好讲的。
银河英雄传说
http://cojs.tk/cogs/problem/problem.php?pid=260
记录附加信息表示到父亲的距离。
食物链
http://cojs.tk/cogs/problem/problem.php?pid=298
记录附加信息表示与父亲的关系。
-
二叉堆
二叉堆是完全二叉树,故可以直接使用一个数组+堆的长度进行存储,节省内存空间。
为了方便叙述,这里默认小根堆。
-
性质
每个节点存储的值均比它的两个儿子小。
-
插入
把元素扔到堆尾,然后往上走,不符合堆性质则交换,直到不需交换为止。
复杂度O(logn)。
-
取最小值
直接返回a[1]即可。
复杂度O(1)。
-
删除最小值
把堆尾扔到根,然后往下走,不符合堆性质则则交换,直到不需交换为止。
复杂度O(logn)。
-
使用STL
参见一.3.b。
-
例题
中位数
http://cojs.tk/cogs/problem/problem.php?pid=1699
开一半的堆,只存较大(较小)的一半。
贴海报
http://cojs.tk/cogs/problem/problem.php?pid=1682
扫描线+堆大法好!!!
-
最近公共祖先
简称LCA。
-
性质与用途
LCA(x,y)是x和y的所有公共祖先中深度最大的一个。
利用LCA可以解决树上两点距离以及其他路径有关问题(通常借助树剖套线段树解决)。
-
ST LCA
在线算法,利用RMQ+欧拉序找LCA。
欧拉序就是dfs序的升级版,第一次访问某节点和访问完其子树时均记录。
一个有n节点的有根树的欧拉序长度总是2n-1。
显然区间[first[x],first[y]]中深度最小的点就是LCA(x,y)。
因为RMQ是静态的,因此采用ST算法实现RMQ(参见六.3.a)。
预处理复杂度O(nlogn)(ST的预处理),查询理论上O(1)。
-
倍增LCA
在线算法,记f[i][j]为i的第2j个祖先,查询LCA(x,y)的时候使用上翻法上翻即可。
预处理复杂度O(nlogn),查询O(logn)。
-
树剖LCA
在线算法,借助树剖求LCA。
预处理复杂度O(n),查询O(logn)。
-
Tarjan LCA
离线算法,需要借助并查集。
dfs回溯时合并各个子树并处理各个询问。
显然z为LCA(x,y)当且仅当x和y刚好在回溯到z时合并到同一集合中,因此就可以按照dfs序先后处理询问。
总复杂度O(n+mlogm),如果使用线性排序则可以降到O(n+m)。
几种LCA算法的比较
算法 |
在线/离线 |
预处理 |
查询 |
代码长度 |
能否维护附加信息 |
代码长度 |
朴素 |
在线 |
无 |
n |
简短 |
能 |
简短 |
ST |
在线 |
nlogn |
1 |
冗长 |
不能 |
—— |
倍增 |
在线 |
nlogn |
logn |
简短 |
能 |
简短 |
树剖 |
在线 |
n |
logn |
一般 |
能 |
一般 |
Tarjan |
离线 |
共O(n) |
冗长 |
能 |
非常冗长 |
-
树链剖分
思想就是把一棵树剖分成若干条链,每一个点属于唯一的一个链。
采用重链剖分法可以使得每个节点到根的路径上最多只有O(logn)条链。
一条链上的点在dfs序中都是相邻的,故处理两点间路径问题时就可以借助LCA来逐链进行区间处理,一般采用套线段树实现。
-
基本方法
进行两次dfs,第一次求出size、prt、son(重儿子),第二次求出dfs序。
求出dfs序之后处理路径问题时就方便多了,因为同一条链上的节点在dfs序中都是相邻的,直接进行区间处理即可。
-
用途
主要应用于两点间路径上的最值和权和等问题。
-
例题
树上操作
http://cojs.tk/cogs/problem/problem.php?pid=1963
单点修改、区间修改、区间求和,树剖套线段树裸题。
难存的情缘
http://cojs.tk/cogs/problem/problem.php?pid=1672
单点修改、区间最值,也是裸题。
树的维护
http://cojs.tk/cogs/problem/problem.php?pid=1583
注意这个既要存最大值也要存最小值。
-
字典树
简称Trie。
可以存储许多单词的集合,并且可以方便的求出LCP(最长公共前缀)。
具体就不再细说了。
-
树上动规
参见四.5。
-
K-D树
还没学……幸亏联赛用不着,听说是骗分神器。
-
图论
搜索
-
深度优先搜索
简称dfs。
按照深度优先顺序进行遍历。
对dfs进行深挖,发现dfs可以用于求无向图的割点和桥以及有向图的强连通分量(Tarjan算法)。
-
广度优先搜索
简称bfs。
按照深度从小到大访问,一般利用队列实现,每次从队头取出一个节点,新增节点压到队尾,循环知道队列为空为止。
-
迭代加深搜索
简称IDdfs,或IDA。
也是深度优先,不过用迭代实现,同时计算顺序和普通dfs不同,有时效率比直接dfs高。
如果加上一个启发函数h来估计当前节点到目标节点还有几层就成了IDA*。
-
启发式搜索
这里不讨论最好优先算法(因为其错误的贪心可能导致WA)而只讨论A*。
A*,启发式搜索算法,与之区别,普通的盲目搜索称为A算法。
A*与Dijkstra的区别就在于引入了估价函数h表示当前节点到目标节点的估计代价,用g代表已花费代价,则每次循环时取f=g+h最小的点即可。
注意,这里g必须>=实际代价,h必须<=实际代价。
A*主要应用于求两点间最短路,当然也可以用来求两点间k短路(但需要借助Dijkstra或SPFA预处理出单源最短路)。
注意,A*同样无法处理负边权,实际上Dijkstra不能处理负边权的原因就在于实际代价可能小于Dijkstra默认选取的估价函数h=0。
最短路
-
Floyd
适用于所有节点对的最短路径。
允许负边权但不允许负权回路。
有向图与无向图都适用。
直接背代码好了,反正特别好记。
复杂度O(n3),常数也比较小,偷懒必备。
-
Dijkstra
单源最短路径算法,通常使用堆优化。
不允许负边权。
有向图与无向图都适用。
本质上就是h=0的A*。
注意如果只需求两点间的最短路径,那么只要在目标点被标记的时候直接退出就可以了。
-
SPFA
也是单源最短路径算法,复杂度O(nE)。
允许负边权,可以找出负权回路。
有向图与无向图都适用。
两种有效的优化(需要用双端队列):
进队前与队首dis比较,比队首小则压队首,否则压队尾。
进队前与队中dis平均值比较,比平均值小则压队首,否则压队尾。
其中第二种优化的效果尤其明显,有时候甚至可以超过堆优化Dijkstra。
注意一点,SPFA碰到稠密图容易跪。
-
A*
参见三.1.d。
主要用于求两点间最短路径。
注意在某些难以进行估价的情况下是不能使用A*的,只能使用Dijkstra。
-
强连通分量
Tarjan和Kosaraju的复杂度都是O(n+m),但是通常Tarjan要快,写起来也容易。
-
Tarjan
通过对原图进行一遍dfs,利用dfn和low值求出强连通分量。
需要用到一个栈。
-
Kosaraju
通过对原图和反图进行两遍dfs求强连通分量。
空间和时间效率均比Tarjan低(常数比较大),不推荐使用。
-
双连通分量
好吧还没学……
生成树
-
最小生成树
简称MST。
Prim和Kruskal算法都用到了贪心。
-
Prim
随便选一个点作为起点,每次选取到“已选取”集合距离最短的点加入集合,最后即得到MST。
-
Kruskal
利用并查集。
按边权从小到大对边处理,如果边连接的两个点不在同一集合中则把边加入MST并将两点合并,所有点都合并到同一集合中后求出的即为MST。
-
其他生成树
有瓶颈生成树(其实就是用Kruskal求出的MST)、次小生成树(不会)、k小生成树(什么鬼)等等,总之联赛好像还用不着。
-
生成树计数
没学咧……
-
有向无环图
简称DAG。
最优化问题
-
计数问题
这两类问题很多都可以转化为DAG上的最长(短)路或者路径计数问题,使用DP即可。
-
与DP的联系
DAG为DP提供了极大的方便,但注意有环图一般不可以DP。
二分图
-
理论
这个太多了……去翻笔记。
-
Hungary算法
就是通过不断找增广路最后求出二分图最大匹配,代码就不贴了。
-
普通图转二分图
对于路径覆盖问题,可以把每个点拆成入点和出点,有向边转化成出点到入点的边,最后进行最大匹配即可。
-
例题
放置机器人
http://cojs.tk/cogs/problem/problem.php?pid=1555
把行和列拆成点(被隔开的拆成多个),在空地的行和列之间连边,最后求最大匹配即可。
-
网络流
目前还没学……
-
动态规划
简称动规,DP。
各种比赛必考题。
背包问题
-
01背包
注意逆序枚举体积。
时间复杂度O(nv),空间复杂度O(v)(使用滚动数组)。
-
完全背包
注意正序枚举体积(和01背包相反)。
复杂度与01背包相同。
-
多重背包
有两种写法:
二进制拆分,时间复杂度O(nv∑logm),其中m代表物品的数目,空间复杂度O(v)。
单调队列,通过倒余数啥的提高效率,时间复杂度O(nv),空间复杂度O(v)。
-
分组背包
注意先枚举组,再枚举体积,最后枚举物品,这样才能做到每组最多只选一个。
时间复杂度O(nv),空间复杂度O(v)。
-
二维费用背包
开两维记录两种费用即可,时间复杂度O(nvu),空间复杂度O(vu)。
多人背包
-
互斥背包
这俩好像还没学……
-
例题
采药(加强版)
http://cojs.tk/cogs/problem/problem.php?pid=2230
其实就是个奇技淫巧,转化为多重背包处理即可。
奶牛渡河
http://cojs.tk/cogs/problem/problem.php?pid=131
转化为完全背包。
天天做实验
http://hzoi.openjudge.cn/ceyan/T027
转化为分组背包方案计数问题。
线性动规
-
最长上升子序列
简称LIS。
记f[i]为以i结尾的LIS,则状态转移方程为:
f[i]=max{f[j]+1},j<i and a[j]<a[i]
最终答案:ans=max{f[i]}
复杂度O(n2),用二分查找优化可以优化到O(nlogn)。
-
最大连续和
记f[i]为以i结尾的最大连续和,则状态转移方程为:
f[i]=max{f[i-1],0}+a[i]
最终答案:ans=max{f[i]}
复杂度O(n)。
子序列动规
-
最长公共子序列
简称LCS。
状态转移方程有两种情况,在此不再详细写出。
复杂度O(nm)。
-
最长公共子串
也简称LCS。
让一个串不动,另一个串从左往右移动,每次找出最长连续相同区域,最后取最大值即可,
复杂度O(nm)。
-
最长公共上升子序列
简称LCIS。
与LIS类似,状态转移也有两种情况,在此不再写出状态转移方程。
复杂度O(nm)。
-
区域动规
一般记f[i][j]为区间[i,j]的答案,循环时从小到大枚举区间长度,最终答案即为f[1][n]。
例题:
合并石子
http://cojs.tk/cogs/problem/problem.php?pid=1658
注意最小得分可以用四边形不等式优化,最大得分直接贪心选最左边或者最右边即可。
能量项链
http://cojs.tk/cogs/problem/problem.php?pid=116
没啥可说的。
加分二叉树
http://cojs.tk/cogs/problem/problem.php?pid=106
其实这不是树规。
树规
-
无根树转有根树
无根树处理起来比较麻烦,转换成有根树比较方便。
一般方法就是随意选一个点为树根,然后dfs或bfs建树,当然某些题目中规定了树根,就不用再转化了。
-
记忆化搜索与非递归写法
树规大多使用记忆化搜索,但是某些题目可以采用非递归写法,常数较记忆化搜索减小很多。
记忆化搜索相当于dfs,而非递归写法是自底向上填表或刷表,具体实现时一般采用bfs逆序填表或刷表(推荐刷表,因为这样只需记录某个节点的父亲而不用记录它的儿子)。
-
例题
选课
http://cojs.tk/cogs/problem/problem.php?pid=1199
树规模板题,注意要采用左儿子右兄弟写法。
火车站饭店
http://cojs.tk/cogs/problem/problem.php?pid=613
注意需要转有根树,并且可以用非递归写法(非递归稳坐榜首)。
人品问题
http://hzoi.openjudge.cn/ceyan/T031
树规模板题,这次是二叉树,不用左儿子右兄弟了。
-
附加信息动态规划
通过在状态中记录一些附加信息来确保状态转移的进行以及结果的正确性。
例题:
技能树
http://cojs.tk/cogs/problem/problem.php?pid=500
记录两个附加信息:资源数目以及本次消耗的资源。
火车站饭店
http://cojs.tk/cogs/problem/problem.php?pid=613
记录一个附加信息(布尔值)表示当前节点有没有选。
小象和老鼠
http://cojs.tk/cogs/problem/problem.php?pid=2335
记录一个附加信息(也是布尔值)表示是从哪个方向走过来的。
-
状态压缩动态规划
通常是用01值压成int来表示一个集合。
例题:
混乱的队伍
http://cojs.tk/cogs/problem/problem.php?pid=2348
用一个int表示有没有在队伍中,再进行计数即可。
旅行商问题(TSP)
有名的NP完全问题,但对于小规模的数据可以用一个int表示是否已经走过,然后就是最优化问题。
-
数学
基础
-
欧几里德距离与曼哈顿距离
欧几里德距离就是两点间的直线距离。
曼哈顿距离就是两点横纵坐标差之和。
-
三角恒等变换与解三角形
三角恒等变换部分有和角公式、差角公式、倍角公式、半角公式、和差化积、积化和差、万能公式等,具体参见王后雄(数学必修4)。
解三角形主要就是正弦定理和余弦定理,这个简单,不再赘述。
计数原理
-
加法原理
当做一件事有几种不同的方法,则总方案数即为所有方法的方案数之和。
-
乘法原理
当做一件事可以分成几步,则总方案数即为每部的方案数之积。
-
与DP联系
动规中的计数问题很大程度上要依赖于两个计数原理。
例题:
混乱的队伍
http://cojs.tk/cogs/problem/problem.php?pid=2348
加法计数。
Hankson的趣味题
http://cojs.tk/cogs/problem/problem.php?pid=405
质因数分解后乘法计数。
排列组合
-
排列数与组合数
:排列数。
:组合数。
计算公式:
-
生成全排列
有两种方法。
递归枚举:手写dfs枚举排列。
下一个排列:手写下一个排列,或者直接用STL的next_permutation()或prev_permutation()。
-
生成组合
也有两种方法:
递归枚举:手写dfs枚举组合。
排列0和1:枚举0和1的全排列,再把每一个排列转化成对应的组合即可。
-
矩阵
矩阵的定义省略。
-
加法,减法,数乘
矩阵的加法和减法必须满足两个同型矩阵(横纵大小均相同)相加减,直接对应位置相加减即可。
数乘直接在每一位置上都乘以乘数即可。
容易发现矩阵的这三种运算都和向量很类似。
-
矩阵乘法
矩阵乘法必须满足两个大小为n*m与m*p的矩阵相乘,结果为一个大小为n*p的矩阵。
设参加运算的两个矩阵分别为a、b,结果为c,则计算公式为
总运算量为n*m*p,朴素算法和一般分治法复杂度都是O(n3),至于什么O(n2.81)的Strassen算法,不学也罢,没啥卵用。
-
应用
很多问题可以构造矩阵,构造出矩阵之后利用矩阵快速幂可以高效解决很多问题。
例题:
随机数生成器
http://cojs.tk/cogs/problem/problem.php?pid=963
构造矩阵然后快速幂,注意要用到快速乘。
概率与期望
-
定义与性质
定义省略。
期望具有可加性及可乘性。
-
应用
超几何分布
给定一个无向图,每个节点等可能地被染上红色和蓝色,求问连接两个不同颜色的点的边的期望数量。
类似2次掷硬币结果不同的概率,显然是|E|/2。
拦截导弹
http://www.lydsy.com/JudgeOnline/problem.php?id=2244
这是神题……
数论
-
同余理论
a≡b(mod p)等价于a mod p=b mod p。
-
费马小定理
p为素数,则ap-1≡1(mod p)。
欧拉定理的特殊情况,没啥卵用。
-
欧拉函数phi
φ(n)表示小于n且与n互质的数的个数。
公式:
其中pk代表n的每个素因数。
运用唯一分解,可以做到在O(sqrt(n))时间内求出单个phi值。
-
欧拉定理
a与p互质,则
aphi(p)≡1(mod p)
用欧拉定理可以求乘法逆元。
-
扩展欧几里德
用来解二元一次方程的整数解。
具体实现,就是通过递归求出一组解,然后回溯推出最小解。
代码就不贴了……
乘法逆元
-
定义
满足a*b≡1(mod p)的b就是a在模p意义下的乘法逆元,一般记做a-1。
注意这里a和p必须互质。
三种求法
-
欧拉定理
利用a-1=aphi(p)-1加上快速幂即可,当然在p为素数的情况下phi(p)=p-1。
在p为素数的情况下可以做到O(logp)(快速幂),但一般情况复杂度O(sqrt(p))(因为要唯一分解)。
注意效率比较低,所以遇到p比较大的情况容易被卡,这时就需要使用扩展欧几里德。
-
扩展欧几里德
构造出方程并求解即可。
复杂度O(logp)(大概)。
-
线性筛
据说是O(p)无常数,不过看样子应用不广……
-
例题
同余方程
http://cojs.tk/cogs/problem/problem.php?pid=1265
裸题,注意欧拉定理会被卡。
Asm.Def大点兵
http://cojs.tk/cogs/problem/problem.php?pid=2037
排列数裸题。
韩信点兵
http://cojs.tk/cogs/problem/problem.php?pid=1786
CRT裸题。
-
中国剩余定理
简称CRT,又名单身狗定理。
懒得写了……总之需要用到乘法逆元。
例题:
韩信点兵
http://cojs.tk/cogs/problem/problem.php?pid=1786
裸题。
丧心病狂的韩信大点兵
http://cojs.tk/cogs/problem/problem.php?pid=2160
需要模法合并,然而并不会,反正暴力能骗70分……
计算
-
自适应simpson函数
就是一暴力,暴力计算函数面积,只不过这玩意儿比较“智能”罢了。
例题:
描边
http://cojs.tk/cogs/problem/problem.php?pid=545
神题……据说正解就是这个。
-
凸包
看书吧……目前好像还没用到过。
-
三分法求单峰函数最值
……没讲过。
原理就是通过不断三分缩小区间,最后找出函数最值以及对应的自变量值。
注意必须是单峰。
-
主方法
用于快速求解递归式。
注意只适用于子问题大小相等的情况。
参见一.6.c。
-
小专题
基本数据结构
-
栈
LIFO(Last In First Out)。
只能在一端进行压栈、读栈和退栈。
例题:
有括号的算术表达式运算
http://cojs.tk/cogs/problem/problem.php?pid=1705
利用两个栈实现中缀表达式的计算。
实现Tarjan强连通分量算法
参见三.3.a。
-
队列
FIFO(First In First Out)。
只能在一端进队,另一端出队。
具体应用有实现bfs等。
-
双端队列
两头都可以进行插入和删除。
应用主要有优化SPFA、单调队列等。
例题:
最佳序列
http://cojs.tk/cogs/problem/problem.php?pid=2382
二分平均数后利用单调队列求长度有限制的最大连续和。
-
链表
这个就是基本功了……不讲。
例题:
约瑟夫问题
http://hzoi.openjudge.cn/ceyan/T005/
虽然这题暴力也可以过,不过(其中一种)正解是双向链表。
-
左儿子右兄弟
这个也是基本功……不讲。
例题:
选课
http://hzoi.openjudge.cn/dg/D017/
由于是多叉树,因此要使用左儿子右兄弟法转换后再树规。
二分
-
二分查找
只适用于有序数组,每次取区间中点比较大小并决策属于左区间或属于右区间或就在中点上。
例题:
拦截导弹加强版
http://hzoi.openjudge.cn/ceyan/T009/
数据规模丧心病狂,所以必须使用二分查找优化的LIS算法。
-
二分答案
与二分查找类似,都是通过不断缩小区间到一个点从而得到答案。
例题:
跳石头
http://cojs.tk/cogs/problem/problem.php?pid=2107
直接二分答案然后搬掉石头最后查结果即可,复杂度O(nlogn)。
最佳序列
http://cojs.tk/cogs/problem/problem.php?pid=2382
二分平均数,然后利用单调队列求长度有限制的最大连续和即可。
整体二分
-
陈丹琦分治
好吧这两个都是什么鬼……表示不会。
倍增
-
Sparse-Table算法
简称ST算法。
只能解决静态RMQ问题。
f[i][j]:以i开头长度为2j的区间最小值。
查询时通过取查询区间中的两个区间的最小值来做到O(1)查询。
预处理O(nlogn),查询理论上讲是O(1)。
代码简单,常数也不大,适合查询很多的情况。
-
倍增LCA
参见二.7.c。
分块
-
思想
把一个一维数组划分成sqrt(n)个块,处理单点修改、单点查询、区间修改、区间查询时就可以做到O(sqrt(n))的效率。
总复杂度一般是O(msqrt(n)),一般对于n<=50000的数据范围很够用。
-
例题
HH的项链
http://cojs.tk/cogs/problem/problem.php?pid=421
数据比较小,因此可以用分块,并且分块是在线的。
-
优缺点
优点:适用范围广,代码简单。
缺点:效率较低,其实也是一种暴力,只不过这玩意儿骗分比较容易罢了。
嵌套
-
二维线段树
说白了就是线段树套线段树。
复杂度O(log2n)。
-
树状数组套主席树
应用于k小数的单点修改版,通过单点修改区间求和维护前缀和主席树。
-
树剖套线段树
树剖一般都要套线段树的……当然也有套树状数组什么的,不过很少。
例题参见二.8.c。
-
map套数据结构
这个很常用啦……并且通常也不会再乘上一个logn。
例题:
2387
http://cojs.tk/cogs/problem/problem.php?pid=2387
std用的map套AVL树,虽然用线段树套map也可以过。
字符串
-
字符串哈希
有两种哈希。
第一种每次都需要O(n)的时间得出hash值,第二种需要O(n)预处理,但查询只需要O(1)。
典型应用就是配合二分答案求最长公共前缀(LCP)。
-
Trie
参见二.9。
-
模式匹配
只介绍MP算法。
MP对于朴素匹配的改进就是引入fail指针来确定失配后应当跳转到哪里。
预处理O(m),匹配O(n+m)。
-
骗分
直接上例题。
sum
http://cojs.tk/cogs/problem/problem.php?pid=2272
暴力+打表,打出所有5000以内的答案,成功骗到50分。
正解据说是NTT什么的鬼东西,总之就是不会。
白雪皑皑
http://cojs.tk/cogs/problem/problem.php?pid=2329
扫描线+堆法,再卡卡常成功AC。
正解采用倒序涂色+链表大法O(n+m),太神啦。
磁性链
http://cojs.tk/cogs/problem/problem.php?pid=858
排列枚举骗到50分。
正解应该是区域DP吧……
tree
http://cojs.tk/cogs/problem/problem.php?pid=2274
不吐槽垃圾数据了,连暴力都能AC,并查集打爆了倒是直接爆零……
正解是并查集倒序处理。
你猜是不是KMP
http://cojs.tk/cogs/problem/problem.php?pid=2216
其实KMP也能骗40分……
正解是FFT什么的,还是不会。
-
总结
学OI一年了,在这一年里,我学到了很多知识,也得到了很多经验。
注意思维的培养。学DP时尤其如此,很多题都是跪在没思路上。
多刷题,这样才能提高代码能力。
注意归纳总结,就像现在我所做的。
感谢OI给我美好的回忆,感谢OI告诉我什么叫梦想,感谢OI为我提供实现梦想的机会。
联赛在即,要想进省队,联赛是第一道坎。打好联赛,才能为省选打下好的基础。
为了我的梦想,努力吧。
The End.
16.7.26 Tue.初步完成,
16.7.28 Thu.进行修改。