强联通分量 缩点 tarjan 入门题小集

来源:互联网 发布:海量数据排序 编辑:程序博客网 时间:2024/06/05 10:08

参考

强联通分量及缩点tarjan算法解析 ——九野的博客
强连通tarjan模版 ——九野的博客

HDU 1269

题意

判断给定的有向图是否强联通,即判断图中的强联通分量数是否为 1.

Code

#include <bits/stdc++.h>#include <stack>#define maxn 100010using 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 1010using 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 50010using 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 0x3f3f3f3fusing 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 0x3f3f3f3fusing 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找最长链/拓扑排序

原创粉丝点击