OI复健计划
由于准备再战XCPC, 因此我要开始复健OI!
图论
2-SAT
感觉之前打oi的时候理解的并不透彻啊. 首先,2-SAT算法的本质是以下两个公式:
因此,当我们遇到一个2-CNF的时候,我们会尝试把其中的所有
你可能会有个疑问: 既然
得到新的公式后,我们就进行建图,并且的确同一个SCC内的原子公式必须取相同值,从而我们必然需要从后往前取SCC,这就有了三种情况:
和 在同一个SCC里,此时必然无解. 在 里, 在 里,并且有 .此时当然只能选择 . 在 里, 在 里,但是 和 没有任何依赖关系,此时随便选一个.
等一下,(3)这里竟然可以随便选一个?那我立刻就想到一种情况啊:比如
为了理解为什么这里不会出矛盾,我们来看一下此时
我们重新回到刚才那个问题:为什么添加
如果
然而这还是有问题,那为什么不可能出现
现在应该容易给出为何只要
我们最后想说明在新得到的图中,一定存在一个"后缀子图"(这名字是我自己编的,但是应该可以理解是什么意思?),使得其恰好完成了上面的选择.为此我们直接倒着拓扑排序开始扩张,如果遇到一个可以加入的SCC就加入.这样做一定可以取出
已经被加入了,因此 不能被加入. 的排序在 之前,意味着 .
可是这意味着
连通分量全家桶
先来看有向图的强连通分量, 这个是在干什么呢? 其实它的目标真的就是缩点.
- 首先, 我们对图进行
dfs并编号dfn, 我们知道如果 是整棵树中dfn最小的那个, 那整个强连通分量一定在以它为根的子树内, 事实上它们还一定是子树中连续的一块. - 因此, 考虑任何一条遍历的链, 如果链上存在两个点在同一个强连通分量中, 它们中间的所有点必定也在一起. 如果用栈维护这个结构, 只需要知道到当前这条链最前面那个点是谁, 这就是
low的作用, 也是为什么low只处理在stack内的dfn. - 由于这个可以一路往上跳, 因此写成
low[x]=std::min(low[x],dfn[v])还是low[x]=std::min(low[x],low[v])并不影响正确性
再来看无向图的边双连通, 很好用的性质是边双等价于存在两条边不相交的路径, 原因是
但是点双性质很差! 原因就在于点双不满足传递性. 不过至少它还在dfs树上是连续的一段. 所以还可以用上述方式求解. 可因为它不满足传递性, 所以它的写法是唯一固定的.
事实上, 对于已经在栈里面的点来说, low[x]=std::min(low[x],dfn[v])其实才是真正自然的写法: 如果你认为low[x]=std::min(low[x],low[v])是自然的写法, 可能是因为你给了low一个更确切的几何意义: 你认为它就代表着能走到的编号最小的点. 但实际上low的功能只是标记一下这里还能不能往上走. 即使按照"能走到的编号最小的点"来理解, 你发现它也是不对的, 因为只扫了一遍, 这个low根本没收敛到这个"解释"上. 如果再做一遍low[x]=std::min(low[x],low[v])的话这个low的值还会变化呢.
顺便一提点双也满足存在两条(除了起点和终点以外)点不相交的路径, 原因是你可以把一个点
不过点双还是有一些好的性质的: 对于一个点双中的两点, 它们之间简单路径的并集恰好完全等于这个点双.
首先, 对于简单路径, 如果你从这个点双走出去了(一定经过某个割点
这个的证明竟然也要途径最大流最小割定理: 考虑中间拆点, 从
有没有更好的证明办法呢? 实际上我们有耳分解: 任何一个点双连通分量, 都可以通过在一个初始简单环上"加耳"得到: 选择图上两个点
耳分解如何证明呢? 考虑一个点双
用耳分解就可以更好地证明上面那个结论: 数学归纳, 假设第
顺便一提边双也可以耳分解, 只是边双允许加耳的两个点
数据结构
莫队
重新学了一下莫队-回滚莫队-二次离线莫队, 总结一下就是:
- 当你发现你可以快速扩大或者缩小区间并且计算答案的时候, 进行莫队.
- 当你发现可以快速扩大区间, 但很难缩小区间的时候, 进行回滚莫队.
- 当你发现扩大区间和缩小区间都带一个额外的复杂度, 尝试二次离线莫队.
评论