如果你学过欧拉回路,就知道,为了保证时间复杂度正确,要在跑图的时候加上当前弧优化,如果你还学过网络流(Dinic),那相信你也知道 Dinic 在执行 dfs 求分层最大流的时候也需要加当前弧优化。
然而学过 Dinic 的同学,在学 Dinic 的时候可能会看到一段非常优美的当前弧优化代码:
for(int &i=head[u];~i;i=edge[i].nxt)
没错在网络流中有没有当前弧优化只取决于 int 后面的 i 前面有没有地址符 & ,原理是什么我不再做过多解释,大家自己也清楚。
然后你可能觉得既然 Dinic 的当前弧优化这么写可以,那么欧拉回路的当前弧优化这么写也可以,于是你高高兴兴地写完了这份简洁而优美的代码并在洛谷上获得一个 AC,你欣喜若狂的用这份代码疯狂在各种欧拉回路题上获得 AC,直到有一天你在一道 CF 题 上出了锅,你死活不知道你哪错了,瞪着你的代码看了半天也发现不出来任何错误,直到 CF 上给你来了这句话。
cpp:81:26: runtime error: index -1 out of bounds for type 'struct line [1000005]'
这时,有 114514 个问号在你的大脑中蹦出,“我明明在循环中判断了 i 不能等于 −1,为什么会在 i=edge[i].nxt 中调用 edge[-1]???
这是因为你的欧拉回路中相同的点可能会在递归栈中出现多次,试想一下,对于相同的点 u,栈中较深的点把 i(即 head[u])拨到了 −1,然后退出循环并退出递归,经过一系列回溯操作回到栈中较浅的点,此时栈中较浅的点的 i 为 −1 但还没有退出循环,根据 for(int &i=head[u];~i;i=edge[i].nxt) 循环的执行规则,在当前这一轮循环结束的时候需要先执行 i=edge[i].nxt 再执行判断操作 ~i,所以就会出现前文中的调用 edge[-1] 的情况。
上文中的“你”指的都是我,你说我是不是个小丑?。
聪明的你可能就想问了,那为什么 Dinic 这么写就没这个问题,因为 Dinic 本质上是分层图跑最大流,递归栈中不可能出现相同点,而上述错误可能出现都是在递归栈中可能有相同点的基础上的,所以大家可以放心用这份代码写 Dinic。
那我们该怎么改这份代码呢?
很简单,只需要在 for 循环中的每一轮将要结束的的时候额外判断一下 i 是否等于 −1 即可,或者可以这么写:
for(int i=head[u];~i;i=head[u]){
head[u]=edge[i].nxt;
}