強聯通分量 縮點 tarjan 入門題小集

參考

強聯通分量及縮點tarjan算法解析 ——九野的博客
強連通tarjan模版 ——九野的博客

HDU 1269

題意

判斷給定的有向圖是否強聯通,即判斷圖中的強聯通分量數是否爲 1 .

Code

#include <bits/stdc++.h>
#include <stack>
#define maxn 100010
using namespace std;
struct Edge {
    int to, ne;
    Edge(int a = 0, int b = 0) : to(a), ne(b) {}
}edge[maxn * 2];
int low[maxn], dfn[maxn], cnt, tot, ne[maxn], n, m, scc;
bool vis[maxn], in[maxn];
stack<int> s;
void add(int u, int v) {
    edge[tot] = Edge(v, ne[u]);
    ne[u] = tot++;
}
void dfs(int u) {
    vis[u] = true;
    low[u] = dfn[u] = cnt++;
    in[u] = true;
    s.push(u);
    for (int i = ne[u]; i != -1; i = edge[i].ne) {
        Edge e = edge[i]; int v = e.to;
        if (!vis[v]) {
            dfs(v);
            low[u] = min(low[u], low[v]);
        }
        else if (in[v]) low[u] = min(low[u], dfn[v]);
    }
    if (dfn[u] == low[u]) {
        ++scc;
        if (scc == 2) return;
        while (true) {
            int x = s.top();
            s.pop(); in[x] = false;
            if (x == u) break;
        }
    }
}
void work() {
    memset(vis, 0, sizeof(vis));
    memset(ne, -1, sizeof(ne));
    memset(dfn, 0, sizeof(dfn));
    memset(low, 0, sizeof(low));
    cnt = tot = scc = 0;
    for (int i = 0; i < m; ++i) {
        int u, v;
        scanf("%d%d", &u, &v);
        add(u, v);
    }
    bool flag = false;
    for (int i = 1; i <= n; ++i) {
        if (!vis[i]) dfs(i);
        if (scc > 1) break;
    }
    if (scc == 1) printf("Yes\n");
    else printf("No\n");
}
int main() {
    while (scanf("%d%d", &n, &m) != EOF && n + m) work();
    return 0;
}

HDU 1827

題意

給定一張有向圖,一個節點爲一個人,邊 uv 代表 u 能通知到 v (邊上也有權值表示通知的代價),求通知到圖中所有人的最小代價。

思路

先縮點,得到一個 DAG ,注意到,只需通知新圖中入度爲 0 的點即可。
另:POJ 2186與之類似,只要找新圖中出度爲 0 的點即可。
DAG 中必存在入度爲 0 和出度爲 0 的點。

Code

#include <bits/stdc++.h>
#define inf 0x3f3f3f3f
#include <stack>
#include <vector>
#define maxn 1010
using namespace std;
typedef long long LL;
stack<int> s;
vector<int> bcc[maxn];
struct Edge {
    int from, to, ne;
    Edge(int a=0, int b =0, int c = 0) : from(a), to(b), ne(c) {}
}edge[maxn * 2];
int dfn[maxn], low[maxn], ne[maxn], belong[maxn], deg[maxn], tot, cnt, scc, val[maxn];
bool in[maxn];
void add(int u, int v) {
    edge[tot] = Edge(u, v, ne[u]);
    ne[u] = tot++;
}
void init() {
    cnt = tot = scc = 0;
    while (!s.empty()) s.pop();
    memset(ne, -1, sizeof(ne));
    memset(dfn, 0, sizeof(dfn));
    memset(low, 0, sizeof(low));
    memset(belong, 0, sizeof(belong));
    memset(deg, 0, sizeof(deg));
    memset(in, 0, sizeof(in));
}
void tarjan(int u) {
    dfn[u] = low[u] = ++cnt;
    in[u] = true;
    s.push(u);
    for (int i = ne[u]; i != -1; i = edge[i].ne) {
        int v = edge[i].to;
        if (!dfn[v]) {
            tarjan(v);
            low[u] = min(low[u], low[v]);
        }
        else if (in[v]) low[u] = min(low[u], dfn[v]);
    }
    if (low[u] == dfn[u]) {
        ++scc;
        bcc[scc].clear();
        while (true) {
            int v = s.top();
            in[v] = false;
            s.pop();
            belong[v] = scc;
            bcc[scc].push_back(v);
            if (v == u) break;
        }
    }
}
void contract() {
    for (int i = 0; i < tot; ++i) {
        int u = edge[i].from, v = edge[i].to;
        if (belong[u] == belong[v]) continue;
        ++deg[belong[v]];
    }
}
int n, m;
void work() {
    init();
    for (int i = 1; i <= n; ++i) scanf("%d", &val[i]);
    for (int i = 0; i < m; ++i) {
        int u, v;
        scanf("%d%d", &u, &v);
        add(u, v);
    }
    for (int i = 1; i <= n; ++i) {
        if (!dfn[i]) tarjan(i);
    }
    contract();
    LL ans = 0; int anc = 0;
    for (int i = 1; i <= scc; ++i) {
        if (deg[i] == 0) {
            ++anc;
            int minn = inf;
            for (auto x : bcc[i]) minn = min(minn, val[x]);
            ans += minn;
        }
    }
    printf("%d %lld\n", anc, ans);
}
int main() {
    while (scanf("%d%d", &n, &m) != EOF) work();
    return 0;
}

HDU 3836

同 hdu2767.

題意

在有向圖中加最少的有向邊使得圖成爲一個強聯通分量。

思路

先縮點。要使新圖成爲一個強聯通分量,形象一點想,即是讓所有的首尾相接,於是統計“首”和“尾”的個數,即入度爲 0 的點和出度爲 0 的點的個數,取最大值即可。注意特判原本即爲一個強聯通分量的情況。

Code

#include <bits/stdc++.h>
#include <stack>
#define maxn 20010
#define maxm 50010
using namespace std;
stack<int> s;
int dfn[maxn], low[maxn], ne[maxn], in[maxn], belong[maxn], ind[maxn], outd[maxn], cnt, tot, scc;
struct Edge {
    int from, to, ne;
    Edge(int a = 0, int b = 0, int c = 0) : from(a), to(b), ne(c) {}
}edge[maxm];
void add(int u, int v) {
    edge[tot] = Edge(u, v, ne[u]);
    ne[u] = tot++;
}
void init() {
    cnt = tot = scc = 0;
    memset(dfn, 0, sizeof(dfn));
    memset(low, 0, sizeof(low));
    memset(ne, -1, sizeof(ne));
    memset(in, 0, sizeof(in));
    memset(ind, 0, sizeof(ind));
    memset(outd, 0, sizeof(outd));
    while (!s.empty()) s.pop();
}
void tarjan(int u) {
    dfn[u] = low[u] = ++cnt;
    in[u] = true;
    s.push(u);
    for (int i = ne[u]; i != -1; i = edge[i].ne) {
        int v = edge[i].to;
        if (!dfn[v]) {
            tarjan(v);
            low[u] = min(low[u], low[v]);
        }
        else if (in[v]) low[u] = min(low[u], dfn[v]);
    }
    if (low[u] == dfn[u]) {
        ++scc;
        while (true) {
            int v = s.top();
            in[v] = false;
            s.pop();
            belong[v] = scc;
            if (u == v) break;
        }
    }
}
void contract() {
    for (int i = 0; i < tot; ++i) {
        int u = edge[i].from, v = edge[i].to;
        if (belong[u] == belong[v]) continue;
        ++outd[belong[u]], ++ind[belong[v]];
    }
}
int n, m;
void work() {
    init();
    while (m--) {
        int u, v;
        scanf("%d%d", &u, &v);
        add(u, v);
    }
    for (int i = 1; i <= n; ++i) {
        if (!dfn[i]) tarjan(i);
    }
    if (scc == 1) {
        printf("0\n");
        return;
    }
    contract();
    int intot = 0, outtot = 0;
    for (int i = 1; i <= scc; ++i) {
        if (!ind[i]) ++intot;
        if (!outd[i]) ++outtot;
    }
    printf("%d\n", max(intot, outtot));
}
int main() {
    while (scanf("%d%d", &n, &m) != EOF) work();
    return 0;
}

吐槽

這道題寫得實在是太不走心了…wa了好幾發
++outd[belong[u]], ++ind[belong[v]];寫成了++outd[u], ++ind[v];
下面枚舉的時候明明應該是新圖的點數 scc ,卻寫成了 n .
真是可怕啊 Orz 要多加註意這些細節纔是。

HDU 4635

題意

給定一個有向圖,問至多加多少條邊之後,圖仍然不強聯通。

分析

(這道題很有意思,和上一道題可以說是恰好反過來~)
正難則反。

如果真要考慮去加邊的話,要考慮:1. 在每個已形成的強聯通分量內加邊;2. 在縮點形成後的圖中加邊,其中還要考慮入/出度爲 0 和不爲 0 的點。反正我是不會做。

考慮先將給定的圖加邊加成完全圖,再從完全圖中刪除儘量少的一些剛剛加入的邊使得圖不強聯通。
1. 加邊很容易,當前圖邊數爲 m ,點數爲 n ,加成完全圖即加入 n(n1)m 條邊;
2. 刪邊的話,一旦將某一個點刪成入/出度爲 0 ,肯定就不強聯通了。(這是充分條件;至於是否是必要條件,即是否有 圖不強聯通 存在一個點其入度或出度爲 0,這一點我就不清楚了 QWQ ,麻煩看到這的讀者老爺指教。)

在縮點後形成的 DAG 上考慮。實際操作的時候,爲使所刪邊最少,肯定會只刪一個點的相關邊,將入邊刪光或將出邊刪光。因爲我們刪邊只能刪除我們 剛剛加入的邊,所以若要能將入邊刪光,則該點原本的入度必然爲 0 ,出邊亦同理。所以,即是去找入度爲 0 (或出度爲 0 )的新點 所對應的 size 最小的 強聯通分量,刪除所有指向它的入邊(或它指出去的出邊),即 (nsize)size 條邊即可。

最後答案即爲 n(n1)m(nsize)size .

Code

#include <bits/stdc++.h>
#define maxn 100010
#include <stack>
#define inf 0x3f3f3f3f
using namespace std;
stack<int> s;
int dfn[maxn], low[maxn], ne[maxn], sz[maxn], belong[maxn], outd[maxn], ind[maxn], tot, cnt, scc, in[maxn], kas;
struct Edge {
    int from, to, ne;
    Edge(int a = 0, int b = 0, int c = 0) : from(a), to(b), ne(c) {}
}edge[maxn];
void add(int u, int v) {
    edge[tot] = Edge(u, v, ne[u]);
    ne[u] = tot++;
}
void init() {
    tot = cnt = scc = 0;
    while (!s.empty()) s.pop();
    memset(ne, -1, sizeof(ne));
    memset(dfn, 0, sizeof(dfn));
    memset(low, 0, sizeof(low));
    memset(outd, 0, sizeof(outd));
    memset(ind, 0, sizeof(ind));
    memset(in, 0, sizeof(in));
}
void tarjan(int u) {
    dfn[u] = low[u] = ++cnt;
    in[u] = true;
    s.push(u);
    for (int i = ne[u]; i != -1; i = edge[i].ne) {
        int v = edge[i].to;
        if (!dfn[v]) {
            tarjan(v);
            low[u] = min(low[u], low[v]);
        }
        else if (in[v]) low[u] = min(low[u], dfn[v]);
    }
    if (low[u] == dfn[u]) {
        ++scc; sz[scc] = 0;
        while (true) {
            int v = s.top();
            in[v] = false;
            s.pop();
            ++sz[scc];
            belong[v] = scc;
            if (v == u) break;
        }
    }
}
void contract() {
    for (int i = 0; i < tot; ++i) {
        int u = edge[i].from, v = edge[i].to;
        if (belong[u] == belong[v]) continue;
        ++outd[belong[u]], ++ind[belong[v]];
    }
}
void work() {
    int n, m;
    init();
    scanf("%d%d", &n, &m);
    for (int i = 0; i < m; ++i) {
        int u, v;
        scanf("%d%d", &u, &v);
        add(u, v);
    }
    for (int i = 1; i <= n; ++i) {
        if (!dfn[i]) tarjan(i);
    }
    if (scc == 1) { printf("Case %d: -1\n", ++kas); return; }
    contract();
    int minn = inf;
    for (int i = 1; i <= scc; ++i) {
        if (!outd[i] || !ind[i]) minn = min(minn, sz[i]);
    }
    printf("Case %d: %lld\n", ++kas, 1LL * n * (n - 1) - m - 1LL * minn * (n - minn));
}
int main() {
    int T;
    scanf("%d", &T);
    while (T--) work();
    return 0;
}

ZOJ 3630

題意

給定一張有向圖,要求刪除其中一個點,使得剩下的圖中最大的強聯通分量的 size 最小,問 size
題目保證一個點至多在一個強聯通分量中。

思路

記原圖中最大的強聯通分量爲 G1 ,次大的爲 G2
所刪之點必然是 G1 中的點,記 G1vi 中最大的強聯通分量爲 Gi
最後的答案必然是 max(G2.size,min{Gi.size|viG1})
枚舉原圖最大的強聯通分量中的點即可。

Code

#include <bits/stdc++.h>
#define maxn 10000
#include <stack>
#include <vector>
#define inf 0x3f3f3f3f
using namespace std;
stack<int> s;
vector<int> bcc[maxn];
int dfn[maxn], low[maxn], ne[maxn], sz[maxn], tot, cnt, scc, in[maxn];
bool exist[maxn];
struct Edge {
    int from, to, ne;
    Edge(int a = 0, int b = 0, int c = 0) : from(a), to(b), ne(c) {}
}edge[maxn];
void add(int u, int v) {
    edge[tot] = Edge(u, v, ne[u]);
    ne[u] = tot++;
}
void init() {
    tot = 0;
    memset(ne, -1, sizeof(ne));
}
void tarjanInit() {
    cnt = scc = 0;
    while (!s.empty()) s.pop();
    memset(dfn, 0, sizeof(dfn));
    memset(low, 0, sizeof(low));
    memset(in, 0, sizeof(in));
}
void tarjan(int u) {
    dfn[u] = low[u] = ++cnt;
    in[u] = true;
    s.push(u);
    for (int i = ne[u]; i != -1; i = edge[i].ne) {
        int v = edge[i].to;
        if (!exist[v]) continue;
        if (!dfn[v]) {
            tarjan(v);
            low[u] = min(low[u], low[v]);
        }
        else if (in[v]) low[u] = min(low[u], dfn[v]);
    }
    if (low[u] == dfn[u]) {
        ++scc; sz[scc] = 0; bcc[scc].clear();
        while (true) {
            int v = s.top();
            in[v] = false;
            s.pop();
            ++sz[scc];
            bcc[scc].push_back(v);
            if (v == u) break;
        }
    }
}
int n, m;
void work() {
    init(); tarjanInit();
    for (int i = 0; i < m; ++i) {
        int u, v;
        scanf("%d%d", &u, &v);
        ++u, ++v;
        add(u, v);
    }
    for (int i = 1; i <= n; ++i) exist[i] = true;
    for (int i = 1; i <= n; ++i) {
        if (!dfn[i]) tarjan(i);
    }

    int ans, idx, idx2;
    if (scc == 1) ans = 0, idx = 1;
    else {
        if (sz[1] >= sz[2]) idx = 1, idx2 = 2;
        else idx = 2, idx2 = 1;
        for (int i = 3; i <= scc; ++i) {
            if (sz[i] >= sz[idx]) { idx2 = idx; idx = i; }
            else if (sz[i] > sz[idx2]) idx2 = i;
        }
        ans = sz[idx2];
    }
    if (sz[idx] == 1) { printf("0\n"); return; }

    vector<int> V = bcc[idx]; int siz = sz[idx];
    memset(exist, 0, sizeof(exist));
    for (int i = 0; i != V.size(); ++i) exist[V[i]] = true;

    int minn = inf;
    for (int i = 0; i != siz; ++i) {
        tarjanInit();
        exist[V[i]] = false;
        for (int i = 0; i != V.size(); ++i) {
            if (exist[V[i]] && !dfn[V[i]]) tarjan(V[i]);
        }
        int maxx = 0;
        for (int j = 1; j <= scc; ++j) {
            maxx = max(maxx, sz[j]);
        }
        minn = min(minn, maxx);
        exist[V[i]] = true;
    }
    ans = max(minn, ans);
    if (ans == 1) ans = 0;
    printf("%d\n", ans);
}
int main() {
    while (scanf("%d%d", &n, &m) != EOF) work();
    return 0;
}

HDU 6165&POJ 2186

麻煩移步本菜另一篇文章_(:з」∠)_
2017多校九 05題 hdu 6165 FFF at Valentine 縮點 dp找最長鏈/拓撲排序

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章