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

  • 割点

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

DFSDFS 时, 设当前点为 kk, low[]low[] 为最高祖先, dfn[]dfn[]dfsdfs 序, totokk 直接相连的点,

  • toto 点被访问过, 则说明 kk 有向上的边, low[k]=min(low[k],dfn[to])low[k]=min(low[k], dfn[to])

  • 否则沿着 toto 继续递归, 回溯时 low[k]=min(low[k],low[to])low[k] = min(low[k], low[to]),

    low[to]low[to]kk 下方, 说明切掉kk, toto 会在下方形成一个独立的环,

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

将边双联通分量缩点 .

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

注意: 只有在栈中, 才能用来更新 lowlow

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

将点双联通分量缩点 .

DFSDFS 时, 设当前节点为 kk, 连向的节点为 toto,

  • toto 访问过且 dfsdfs序比 kk 小, 说明 totokk 为一个环的首尾, 将边 k,tok, to 加入栈中 .
  • toto 没有访问过, 先将边 k,tok, to 加入栈中, 递归处理 toto, 然后检查 low[to]low[to] 是否大于等于 dfn[k]dfn[k],
    若小于, 则不用管, 会在 low[to]low[to] 处通过弹栈操作处理掉 首 为 low[to]low[to] 的环,
    若大于等于, 说明 toto 可能在 kk 以下构成了环, 也可能与 kk 在同一个环内,
    • 对第一种情况, kktoto 通过弹栈操作仍然能构成点双联通 .
    • 对第二种情况, kktotoDFSDFS kk 这层可以通过弹栈操作找出环 .
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
posted @ 2019-07-19 20:47  XXX_Zbr  阅读(128)  评论(0)    收藏  举报