参考
强联通分量及缩点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
题意
给定一张有向图,一个节点为一个人,边
思路
先缩点,得到一个
DAG
,注意到,只需通知新图中入度为
0
的点即可。
另:POJ 2186与之类似,只要找新图中出度为
DAG
中必存在入度为
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
的点和出度为
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
.
真是可怕啊
HDU 4635
题意
给定一个有向图,问至多加多少条边之后,图仍然不强联通。
分析
(这道题很有意思,和上一道题可以说是恰好反过来~)
正难则反。
如果真要考虑去加边的话,要考虑:1. 在每个已形成的强联通分量内加边;2. 在缩点形成后的图中加边,其中还要考虑入/出度为
0
和不为 反正我是不会做。
考虑先将给定的图加边加成完全图,再从完全图中删除尽量少的一些刚刚加入的边使得图不强联通。
1. 加边很容易,当前图边数为
m
,点数为
2. 删边的话,一旦将某一个点删成入/出度为
0
,肯定就不强联通了。(这是充分条件;至于是否是必要条件,即是否有 图不强联通
在缩点后形成的
DAG
上考虑。实际操作的时候,为使所删边最少,肯定会只删一个点的相关边,将入边删光或将出边删光。因为我们删边只能删除我们 刚刚加入的边,所以若要能将入边删光,则该点原本的入度必然为
0
,出边亦同理。所以,即是去找入度为
最后答案即为 n(n−1)−m−(n−size)∗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
中的点,记
G1−vi
中最大的强联通分量为
Gi
,
最后的答案必然是
max(G2.size,min{Gi.size|vi∈G1})
。
枚举原图最大的强联通分量中的点即可。
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找最长链/拓扑排序