Tarjan [割点, 缩点, 桥(待填坑)]

  • 割 点 割点

删除这个点后, 图的联通块数量变多.

D F S DFS DFS 时, 设当前点为 k k k, l o w [ ] low[] low[] 为最高祖先, d f n [ ] dfn[] dfn[] d f s dfs dfs 序, t o to to k k k 直接相连的点,

  • t o to to 点被访问过, 则说明 k k k 有向上的边, l o w [ k ] = m i n ( l o w [ k ] , d f n [ t o ] ) low[k]=min(low[k], dfn[to]) low[k]=min(low[k],dfn[to])

  • 否则沿着 t o to to 继续递归, 回溯时 l o w [ k ] = m i n ( l o w [ k ] , l o w [ t o ] ) low[k] = min(low[k], low[to]) low[k]=min(low[k],low[to]),

    l o w [ t o ] low[to] low[to] k k k 下方, 说明切掉 k k k, t o to to 会在下方形成一个独立的环,

    1. k k k 不为根节点, k k k割点 ,
    2. k k k 为根节点, 必须有两个以上儿子才算是割点 .
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;
                        }
                }
        }
}
  • 缩 点 ( 边 双 ) 缩点(边双) ()

将边双联通分量缩点 .

D F S DFS DFS 时, 定义同上, 每次将当前节点压入 中,
l o w [ k ] = d f n [ k ] low[k]=dfn[k] low[k]=dfn[k] 时, 说明 k k k节点以下在 中的元素共同构成一个 强联通分量,
此时弹出 内元素, 得到新的环.
与上方判环的方式不同的是, 这里开了个 i n _ s t k [ ] in\_stk[] in_stk[] 数组, 来判断是否在环内.

注意: 只有在栈中, 才能用来更新 l o w low low

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();
        }
}
  • 缩 点 ( 点 双 ) 缩点(点双) ()

将点双联通分量缩点 .

D F S DFS DFS 时, 设当前节点为 k k k, 连向的节点为 t o to to,

  • t o to to 访问过且 d f s dfs dfs序比 k k k 小, 说明 t o to to k k k 为一个环的首尾, 将边 k , t o k, to k,to 加入栈中 .
  • t o to to 没有访问过, 先将边 k , t o k, to k,to 加入栈中, 递归处理 t o to to, 然后检查 l o w [ t o ] low[to] low[to] 是否大于等于 d f n [ k ] dfn[k] dfn[k],
    若小于, 则不用管, 会在 l o w [ t o ] low[to] low[to] 处通过弹栈操作处理掉 首 为 l o w [ t o ] low[to] low[to] 的环,
    若大于等于, 说明 t o to to 可能在 k k k 以下构成了环, 也可能与 k k k 在同一个环内,
    • 对第一种情况, k k k t o to to 通过弹栈操作仍然能构成点双联通 .
    • 对第二种情况, k k k t o to to D F S DFS DFS k k k 这层可以通过弹栈操作找出环 .
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]);
                }
        }
}

  • 相 关 例 题 相关例题

  1. 例题1
  2. 例题2
  3. 例题3
  4. 例题4
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值