双连通学习笔记

DFS树博客:链接
双连通是对无向图所说的

边双:对于一个图G,和子图G2,如果去除图中任意一个边子图G2仍然连通的话,G2中的边就是边双联通

  1. 关于边双点双桥割点的DFS做法
    跟OIwiki学的:链接

对图跑出一颗DFS树。
如图,图源OIwiki,黑边绿边为树边,红边为非树边。被红边所关照两点的树上路径必然不是桥,除此之外的就都是桥了,所以求桥的方法就是用差分。对于非树边u-v, 假设v为更深的树点,那么开个差分数组a[v]++, a[u]–;在DFS回溯的过程中差分,这样就标记好了。两点是否边双连通,就看树上路径是否有桥
在这里插入图片描述
如下图,黑边为树边,红遍为非树边,将非树边两点所对应的树上路径加入一个联通块。这样如果一个点周边的所有的边都属于一个联通块,那么它不是割点,此外就是。点双连通,条件为两点间的路径上的边都为一个联通块。
在这里插入图片描述
2. Tarjan做法
A. 判断割点:
我们开两个数组一个为dfn时间戳代表我们dfs访问点的顺序,另一个为low,这个数组不知道该怎么定义,我觉得可以这样说,它所连接的路径直接返回时间戳最小值。对于判割点的算法应该是这样的,对于求scc的按板子定义吧。
这样的话一个点u直接相连的点v如果有一个不能返回到比dfn[u]小的时间戳,那么该点为割点,如果这个点是根节点的话,需要特判,当它的儿子数>=2的时候它也是割点。
例题1:洛谷P3388求哪些是割点
代码:

#include <bits/stdc++.h>
using namespace std;
#define IO ios::sync_with_stdio(false);cin.tie(0)
#define forn(i, n) for(int i = 0; i < n; ++i)
#define for1(i, n) for(int i = 1; i <= n; ++i)

const int maxn = 2e4 + 5;
int ans, ind;
int dfn[maxn], low[maxn];
bool pcut[maxn];
vector<int>e[maxn],res;

inline void tarjan(int u, int pre) {
    dfn[u] = low[u] = ++ind;
    int son = 0;
    for(auto v : e[u]) {
        if(!dfn[v]) {
            ++son;
            tarjan(v, u);
            low[u] = min(low[u], low[v]);
            if(u != pre && !pcut[u] && low[v] >= dfn[u]) {
                pcut[u] = 1;
                ++ans;
                res.push_back(u);
            }
        }else if(v != pre) low[u] = min(low[u], dfn[v]);
    }
    if(u == pre && !pcut[u] && son > 1) {
        pcut[u] = 1;
        ++ans;
        res.push_back(u);
    } 
}

int main() {
    IO;
    int n, m; cin >> n >> m;
    forn(i, m) {
        int u, v; cin >> u >> v;
        e[u].push_back(v);
        e[v].push_back(u);
    }
    for1(i, n) if(!dfn[i]) tarjan(i, i);
    cout << ans << '
';
    sort(res.begin(), res.end());
    for(auto x : res) cout << x << ' ';
    return 0;
}

例题2:POJ2117求一个图删除一个点之后,联通块最多有多少。

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<string>
#include<cstring>
#include<queue>
#include<stack>
#include<map>
#include<set>
#include<cmath>
#include<sstream>
using namespace std;
#define IO ios::sync_with_stdio(false);cin.tie(0)
#define forn(i, n) for(int i = 0; i < n; ++i)
#define for1(i, n) for(int i = 1; i <= n; ++i)

const int maxn = 1e4 + 5;
int ans, ind, ma;
int dfn[maxn], low[maxn], pcut[maxn];
vector<int>e[maxn];
inline void init(int n) {
    ans = 0, ind = 0, ma = 0;
    forn(i, n + 5) {
        e[i].clear();
        dfn[i] = pcut[i] = 0;
    }
}

inline void tarjan(int u, int pre) {
    dfn[u] = low[u] = ++ind;
    int son = 0;
    for(int i = 0; i < e[u].size(); ++i) {
        int v = e[u][i];
        if(!dfn[v]) {
            ++son;
            tarjan(v, u);
            low[u] = min(low[u], low[v]);
            if(u != pre && low[v] >= dfn[u]) {
                //cerr << "!@ " <<  u << ' '<< v << '
';
                ++pcut[u];
                ma = max(ma, pcut[u]);
            }
        }else if(v != pre) low[u] = min(low[u], dfn[v]);
    }
    if(u == pre && son > 1) {
        //cerr << "!@2 " <<  u  << '
';
        pcut[u] = son - 1;
        ma = max(ma, pcut[u]);
    }
}

int main() {
    IO;
    int n, m;while(cin >> n >> m && (n || m)) {
        init(n);
        forn(i, m) {
            int u, v; cin >> u >> v;
            ++u, ++v;
            e[u].push_back(v);
            e[v].push_back(u);
        }
        for1(i, n) if(!dfn[i]) tarjan(i, i), ++ans;
        ans += ma;
        cout << min(ans, n - 1) << '
';
    }
    return 0;
}

B.求桥:
如果一条边所连的u点和v点,low[v] > dfn[u]那么代表此条边为桥。
例题1:HDU4738
代码:

#include <bits/stdc++.h>
using namespace std;
#define IO ios::sync_with_stdio(false);cin.tie(0)
#define forn(i, n) for(int i = 0; i < n; ++i)
#define for1(i, n) for(int i = 1; i <= n; ++i)

const int inf = 2e9;
const int maxn = 1005;
vector<pair<int, int> >e[maxn];
int ind, mi = inf;
int val[maxn], dfn[maxn], low[maxn];
bool bri[maxn];

inline void init(int n) {
    ind = 0, mi = inf;
    for(int i = 0; i < n + 5; ++i) {
        dfn[i] = bri[i] = 0;
        e[i].clear();
    }
}

inline void tarjan(int u, int pre) {
    dfn[u] = low[u] = ++ind;
    bool ok = 0;
    for(auto &x : e[u]) {
        int v = x.first, w = x.second;
        val[v] = w;
        if(!dfn[v]) {
            tarjan(v, u);
            low[u] = min(low[u], low[v]);
            if(low[v] > dfn[u]) {
                bri[v] = 1;
                mi = min(mi, w);
            }
        }else {
            if(v != pre) low[u] = min(low[u], dfn[v]);
            else {
                if(ok) low[u] = min(low[u], dfn[v]);
                ok = 1;
            }
        }
    }
}

int main() {
    IO;
    //freopen("in.txt", "r", stdin);
    int n, m; while(cin >> n >> m && (n || m)) {
        /*if(!m) {
            cout << 0 << '
';
            continue;
        }*/
        forn(i, m) {
            int u, v, w;
            cin >> u >> v >> w;
            e[u].push_back({v, w});
            e[v].push_back({u, w});
        }
        int cnt = 0;
        for1(i, n) if(!dfn[i]) tarjan(i, i), ++cnt;
        if(cnt > 1) cout << 0 << '
';
        else if(mi == inf) cout << -1 << '
';
        else cout << max(mi, 1) << '
';
        init(n);
    }
    return 0;
}

例题2:HDU2460 一张图,m次操作,每次操作在u, v间添加一条边,然后当前图中桥个数
思路:很显然和上面将的DFS一样,u,v加一条边会使得u v树上路径的所以边变为非桥边。利用跟并查集类似的做法就可以做到线性复杂度了。
代码:

#include <bits/stdc++.h>
using namespace std;
#define IO ios::sync_with_stdio(false);cin.tie(0)
#define forn(i, n) for(int i = 0; i < n; ++i)
#define for1(i, n) for(int i = 1; i <= n; ++i)
#pragma comment(linker,"/STACk:10240000,10240000")

const int maxn = 1e5 + 5;
vector<int>e[maxn];

bool bri[maxn];
int n, m, ans, ind;
int dfn[maxn], low[maxn], fa[maxn], deep[maxn], par[maxn];

inline void init(int n) {
    ans = ind = 0;
    forn(i, n + 5) {
        e[i].clear();
        fa[i] = deep[i] = dfn[i] = bri[i] = 0;
        par[i] = i;
    }
}
inline void tarjan(int u, int pre, int d) {
    bool ok = 0;
    fa[u] = pre, deep[u] = d;
    dfn[u] = low[u] = ++ind;
    for(auto v : e[u]) {
        if(!dfn[v]) {
            tarjan(v, u, d + 1);
            low[u] = min(low[u], low[v]);
            if(!bri[v] && low[v] > dfn[u]) {
                ++ans;
                bri[v] = 1;
            }
        }else {
            if(v != pre) low[u] = min(low[u], dfn[v]);
            else {
                if(ok) low[u] = min(low[u], dfn[v]);
                ok = 1;
            }
        } 
    }
}

inline int find(int x) {
    return x == par[x] ? x : par[x] = find(par[x]);
}

inline void change(int u, int v) {
    u = find(u), v = find(v);
    //cerr << "!@#  " << u << ' ' << v << '
';
    if(u == v) return;
    vector<int>tmp;
    while(u != v) {
        if(bri[u]) bri[u] = 0, --ans;
        if(bri[v]) bri[v] = 0, --ans;
        if(deep[u] > deep[v]) {
            tmp.push_back(u);
            u = fa[u], u = par[u];
        }else if(deep[u] < deep[v]) {
            tmp.push_back(v);
            v = fa[v], v = par[v];
        }else {
            tmp.push_back(u);
            tmp.push_back(v);
            u = fa[u], u = par[u];
            v = fa[v], v = par[v];
        }
    }
    for(auto x : tmp) par[x] = u;
}

int main() {
    //freopen("in.txt", "r", stdin);
    IO;
    int T = 0;
    forn(i, maxn) par[i] = i;
    while(cin >> n >> m && (n || m)) {
        ++T;
        forn(i, m) {
            int u, v; cin >> u >> v;
            e[u].push_back(v);
            e[v].push_back(u);
        }
        tarjan(1, 1, 1);
        cout << "Case " << T << ":
";
        int q; cin >> q;
        while(q--) {
            int u, v; cin >> u >> v;
            change(u, v);
            cout << ans << '
';
            if(!q) cout << '
';
        }
        init(n);
    }
    return 0;
}

C. 求双联通块:
在DFS的过程中开个栈,存下DFS访问的点。
当low[v] >= dfn[u],也就是对u造成割点判断的时候,就分一个双联通块,然后把Stack 吐到u-v这条边。

例题:
洛谷P3225 (WF2011的一题)在一个无向图上选择尽量少的点涂黑,使得任意删除一个点后,每个连通分量至少有一个 黑点。去最小黑点数和满足黑点数的方案数。
做法:
不能涂到割点,因为割点没了,它所分开的联通块都没有黑点。
对于一个双连通块,涂除割点以外的点。但是如果一个双联通块有两个割点,那么他就不需要涂了,因为它总能链接到其他双联通块。
代码:

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define forn(i, n) for(int i = 0; i < n; ++i)
#define for1(i, n) for(int i = 1; i <= n; ++i)
#define IO ios::sync_with_stdio(false);cin.tie(0)

const int maxn = 505;

struct edge {
    int v, nex;
}e[maxn << 1];

bool pcut[maxn << 1];
int tot, id, cnt;
pair<int, int> a[maxn];
int head[maxn << 1], dfn[maxn << 1], low[maxn << 1], color[maxn << 1];
vector<int> bcc[maxn << 1];
stack<pair<int, int> > s;

inline void add(int u, int v) {
    e[++tot] = {v, head[u]}, head[u] = tot;
    e[++tot] = {u, head[v]}, head[v] = tot;
}
inline void init() {
    tot = id = cnt =  0;
    forn(i, maxn << 1) {
        head[i] = dfn[i] = pcut[i] = color[i] = 0;
        bcc[i].clear();
    }
    while(!s.empty()) s.pop();
}
inline void tarjan(int u, int pre) {
    dfn[u] = low[u] = ++id;
    int son = 0;
    for(int i = head[u]; i; i = e[i].nex) {
        int v = e[i].v;
        if(!dfn[v]) {
            s.push({u, v});
            ++son;
            tarjan(v, u);
            low[u] = min(low[u], low[v]);
            if(low[v] >= dfn[u]) {
                pcut[u] = 1;
                ++cnt;
                while(1) {
                    auto now = s.top(); s.pop();
                    int x = now.first, y = now.second;
                    if(color[x] != cnt) bcc[cnt].push_back(x), color[x] = cnt;
                    if(color[y] != cnt) bcc[cnt].push_back(y), color[y] = cnt;
                    if(x == u && y == v) break;     
                }
            }
        }else if(v != pre && dfn[v] < dfn[u]) {
            low[u] = min(low[u], dfn[v]);
            s.push({u, v});
        }
    }
    if(u == pre && son <= 1) pcut[u] = 0;
}

int main() {
    //freopen("a.txt", "w", stdout);
    IO;
    int n, T = 0; while(cin >> n && n) {
        ++T;
        vector<int> has;
        for1(i, n) {
            cin >> a[i].first >> a[i].second;
            has.push_back(a[i].first);
            has.push_back(a[i].second);
        }
        sort(has.begin(), has.end());
        has.erase(unique(has.begin(), has.end()) , has.end());
        for1(i, n) {
            int u = lower_bound(has.begin(), has.end(), a[i].first) - has.begin() + 1;
            int v = lower_bound(has.begin(), has.end(), a[i].second) - has.begin() + 1;
            a[i].first = u, a[i].second = v;
            add(u, v);
        }
        for(auto x : has) {
            if(!dfn[x]) tarjan(x, x);
        }
        ll ans1 = 0, ans2 = 1;
        for1(i, cnt) {
            int cntt = 0;
            //cout << "@!#@!  " << i << '
';
            for(auto x : bcc[i]) {
                if(pcut[x]) ++cntt;
                //cout << x << ' ' << pcut[x] << ' ';
            }
            if(cntt == 1) {
                ++ans1;
                ans2 *= bcc[i].size() - 1;
            }
            //cout << '
';
        }
        if(cnt == 1) ans1 = 2, ans2 = bcc[1].size() * (bcc[1].size() - 1) / 2;
        cout << "Case " << T << ": ";
        cout << ans1 << ' ' << ans2 << '
';
        init();
    }
    return 0;
}

D. 求边双连通
把桥边摘出来,剩下的边跑有几个联通块,每个联通块就是边双。
例题:2019 Yokohama Regional I题(日本区金牌题)
给一个无向图,n个人第i个人从ai点走到bi点,现在让给无向图定向,使得每个人都能通过有向边从ai走到bi。
是否有解,输出方案。
做法:
对边双,DFS树上的边正向,非树边反向。剩下的桥所相连着两个边双,我们把边双缩点。缩点后的图是棵树,边都是桥。
然后对着这个图去跑询问,如果ai 和 bi不在新图的一个联通块,不可能达到。此外找到ai 和 bi的lca,对3点做差分,开两个差分数组,一个数组表示边方向逆树访问方向,另一个数组表示边方向顺树访问方向。然后差分如果两个数组同时有值就代表不可能。
代码:

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define forn(i, n) for(int i = 0; i < n; ++i)
#define for1(i, n) for(int i = 1; i <= n; ++i)
#define IO ios::sync_with_stdio(false);cin.tie(0)

const int maxn = 1e4 + 5;
const int maxm = 1e5 + 5;

vector<pair<int,int> >G[maxn];
struct edage{
    int u, v, nex;
}e[maxm << 1];
int tim, tot = 1, bccnt, sccnt;
int dfn[maxn], low[maxn], Dp[maxn], head[maxn], bcc[maxn], scc[maxn], fa[maxn][20], deep[maxn], st[maxn], ed[maxn], pp[maxn], a[maxn], b[maxn];
bool bri[maxm << 1], ok[maxm << 1], vis[maxn];
inline void add(int u, int v) {
    e[++tot] = {u, v, head[u]}, head[u] = tot;
    e[++tot] = {v, u, head[v]}, head[v] = tot;
}
inline void tarjan(int u, int pre) {
    dfn[u] = low[u] = ++tim;
    for(int i = head[u]; i; i = e[i].nex) {
        int v = e[i].v;
        if(v == pre) continue;
        if(!dfn[v]) {
            tarjan(v, u);
            low[u] = min(low[u], low[v]);
            if(low[v] > dfn[u]) bri[i]  = bri[i ^ 1] = 1;
        }else low[u] = min(low[u], dfn[v]);
    }
}
inline void dfs(int u, int pre, int d) {
    bcc[u] = bccnt, Dp[u] = d;
    for(int i = head[u]; i; i = e[i].nex) {
        int v = e[i].v;
        if(v == pre) continue;
        if(bri[i]) continue;
        if(!bcc[v]) {
            ok[i] = 1;
            dfs(v, u, d + 1);
        }else if(Dp[u] > Dp[v])ok[i] = 1;
    }
}
inline void dfs2(int u, int pre, int d) {
    scc[u] = sccnt, deep[u] = d, fa[u][0] = pre;
    for(int i = 1; deep[u] - (1 << i) >= 0; ++i) {
        fa[u][i] = fa[fa[u][i - 1]][i - 1];
    }
    for(auto &x : G[u]) {
        int v = x.first;
        if(scc[v]) continue;
        pp[v] = x.second;
        dfs2(v, u, d + 1);
    }
}
inline int LCA(int u, int v) {
    if(deep[u] < deep[v]) swap(u, v);
    for(int i = 14; i >= 0; --i) {
        if(deep[fa[u][i]] >= deep[v]) {
            u = fa[u][i]; 
        }
    }
    if(u == v) return u;
    for(int i = 14; i >= 0; --i) {
        if(fa[u][i] != fa[v][i]) u = fa[u][i], v = fa[v][i]; 
    }
    return fa[u][0];
}

inline void getans(int u) { 
    vis[u] = 1;
    a[u] = st[u], b[u] = ed[u];
    for(auto &x : G[u]) {
        int v = x.first;
        if(vis[v]) continue;
        getans(v);
        a[u] += a[v], b[u] += b[v];
    }
    if(a[u] && b[u]) {
        cout << "No" << '
';
        exit(0);
    }
    else if(a[u]) ok[pp[u] ^ 1] = 1;
    else ok[pp[u]] = 1;
}
int main() {
    IO;
    int n, m; cin >> n >> m;
    forn(i, m) {
        int u, v; cin >> u >> v;
        add(u, v);
    }
    for1(i, n) if(!dfn[i]) tarjan(i, i);
    for1(i, n) if(!bcc[i]) ++bccnt, dfs(i, i, 1);
    for(int i = 2; i <= tot; ++i)  if(bri[i]) {
        int u = bcc[e[i].u], v = bcc[e[i].v];
        G[u].push_back({v, i});
    }
    vector<int> root;
    for1(i, bccnt) if(!scc[i]) ++sccnt, dfs2(i, i, 1), root.push_back(i);
    int q; cin >> q;
    forn(i, q) {
        int u, v; cin >> u >> v;
        u = bcc[u], v = bcc[v];
        int lca = LCA(u, v);
        if(scc[u] != scc[v]) return cout <<"No
", 0;
        ++st[u], --st[lca];
        ++ed[v], --ed[lca];
    } 
    for(auto x : root) getans(x);    
    cout << "Yes" << '
';
    for(int i = 2; i <= tot; ++i) {
        if(!ok[i]) continue;
        cout << e[i].u << ' ' << e[i].v << '
';     
    }
    return 0;
}
原文地址:https://www.cnblogs.com/AlexPanda/p/12520271.html