删除这个点后, 图的联通块数量变多.
时, 设当前点为 , 为最高祖先, 为 序, 为 直接相连的点,
-
若 点被访问过, 则说明 有向上的边,
-
否则沿着 继续递归, 回溯时 ,
若 在 下方, 说明切掉, 会在下方形成一个独立的环,- 若 不为根节点, 为 割点 ,
- 若 为根节点, 必须有两个以上儿子才算是割点 .
void Tarjan(int k){
low[k] = dfn[k] = ++ tim;
int cnt = 0, siz = 0;
for(reg int i = head[k]; i; i = edge[i].nxt){
int to = edge[i].to;
if(dfn[to]) low[k] = std::min(low[k], dfn[to]);
else {
siz ++;
Tarjan(to);
low[k] = std::min(low[k], low[to]);
if(low[to] >= dfn[k]){
cnt ++;
if(k != root || cnt > 1) cut[k] = 1;
}
}
}
}
将边双联通分量缩点 .
时, 定义同上, 每次将当前节点压入 栈 中,
当 时, 说明节点以下在 栈 中的元素共同构成一个 强联通分量,
此时弹出 栈 内元素, 得到新的环.
与上方判环的方式不同的是, 这里开了个 数组, 来判断是否在环内.
注意: 只有在栈中, 才能用来更新
void Tarjan(int k){
Dfn[k] = low[k] = ++ t_num;
stk.push(k);
in_stk[k] = 1;
for(int i = head[k]; i; i = edge[i].next){
int t = edge[i].to;
if(!Dfn[t]) Tarjan(t), low[k] = std::min(low[k], low[t]); //WRONG #1
else if(in_stk[t]) low[k] = std::min(low[k], Dfn[t]);
}
if(Dfn[k] == low[k]){
block[k] = ++ block_num;
block_size[block_num] ++;
while(stk.top() != k){
block[stk.top()] = block_num;
block_size[block_num] ++;
in_stk[stk.top()] = 0;
stk.pop();
}
in_stk[k] = 0;
stk.pop();
}
}
将点双联通分量缩点 .
时, 设当前节点为 , 连向的节点为 ,
- 若 访问过且 序比 小, 说明 和 为一个环的首尾, 将边 加入栈中 .
- 若 没有访问过, 先将边 加入栈中, 递归处理 , 然后检查 是否大于等于 ,
若小于, 则不用管, 会在 处通过弹栈操作处理掉 首 为 的环,
若大于等于, 说明 可能在 以下构成了环, 也可能与 在同一个环内,- 对第一种情况, 和 通过弹栈操作仍然能构成点双联通 .
- 对第二种情况, 和 在 这层可以通过弹栈操作找出环 .
void Tarjan(int k, int fa){
low[k] = dfn[k] = ++ tim;
for(reg int i = head[k]; i; i = edge[i].nxt){
int to = edge[i].to;
if(!dfn[to]){
stk.push(k), stk_2.push(to);
Tarjan(to, k), low[k] = std::min(low[k], low[to]);
if(low[to] < dfn[k]) continue ;
bk_cnt ++;
while(!stk.empty()){
int tp = stk.top(), tu = stk_2.top();
bk[bk_cnt].pb(tp), bk[bk_cnt].pb(tu);
stk.pop(), stk_2.pop();
if(tp == k && tu == to) break ;
}
}else if(dfn[to] < dfn[k] && to != fa){
stk.push(k), stk_2.push(to);
low[k] = std::min(low[k], dfn[to]);
}
}
}