無向圖
- 概念
-
時間戳
\(dfn[x]\),在深度優先遍歷中,按照每個節點第一次被訪問的順序,依次做整數標記 -
追溯值
\(low[x]\),通過非搜索邊能到達的最小時間戳
-
割邊判定法則
- 無向邊\((x,y)\)是割邊/橋,當且僅當存在x的一個子節點滿足\(dfn[x] < low[y]\)
刪除無向邊\((x,y)\)后,圖斷開成兩個部分
Code
int dfn[N], low[N], dfcnt;
bool g[M];
void tarjan(int x, int ei) {
dfn[x] = low[x] = ++dfcnt;
for(int i = head[x]; i; i = e[i].next) {
int y = e[i].t;
if (!dfn[y]) {
tarjan(y, i);
low[x] = min(low[x], low[y]);
if (dfn[x] < low[y]) g[i] = g[i^1] = 1;
}
else if (i != (ei^1)) low[x] = min(low[x], dfn[y]);
}
}
割點判定法則
- 若x不是根節點,則x是割點當且僅當存在一個子節點y滿足\(dfn[x]\leq low[y]\)
若x是根節點,則x是割點當且僅當存在至少兩個子節點\(y_1,y_2\)滿足上條件
Code
int dfn[N], low[N], dfcnt, rt;
bool g[N];
void tarjan(int x) {
dfn[x] = low[x] = ++dfcnt;
int son = 0;
for (int i = head[x]; i; i = e[i].next) {
int y = e[i].t;
if (!dfn[y]) {
tarjan(y);
low[x] = min(low[x], low[y]);
if (dfn[x] <= low[y]) {
son++;
if (x != rt || son > 1) g[x] = 1;
}
}
else low[x] = min(low[x], dfn[y]);
}
}
點雙聯通分量
- 對於,每個點雙中來說,圖里是不存在割點的
於是,這里可以就可以將圖轉成一顆圓方樹了。
Code
int dfn[N], low[N], dfcnt, sta[N], top, cnt;
vector<int> dcc[N];
bool g[N];
void tarjan(int x, int rt) {
dfn[x] = low[x] = ++dfcnt;
sta[++top] = x;
int son = 0;
for (int i = head[x]; i; i = e[i].next) {
int y = e[i].t;
if (!dfn[y]) {
tarjan(y);
low[x] = min(low[x], low[y]);
if (dfn[x] <= low[y]) {
son++;
if (x != rt || son > 1) g[x] = 1;
dcc[++cnt].clear();
while (1) {
int z = sta[top--];
dcc[cnt].push_back(z);
if (y == z) break;
}
dcc[cnt].push_back(x);
}
}
else low[x] = min(low[x], dfn[y]);
}
}
邊雙聯通分量
- 對於一個邊雙,任意兩個點都有兩條不重合的路徑
Code
int dfn[N], low[N], dfcnt;
bool g[M];
void tarjan(int x, int ei) {
dfn[x] = low[x] = ++dfcnt;
for(int i = head[x]; i; i = e[i].next) {
int y = e[i].t;
if (!dfn[y]) {
tarjan(y, i);
low[x] = min(low[x], low[y]);
if (dfn[x] < low[y]) g[i] = g[i^1] = 1;
}
else if (i != (ei^1)) low[x] = min(low[x], dfn[y]);
}
}
int n, m, d[N], b[N], cnt, ans;
void dfs(int x) {
b[x] = cnt;
for(int i = head[x]; i; i = e[i].next) {
int y = e[i].t;
if (b[y] || g[i]) continue;
dfs(y);
}
}
int main() {
//~~~
for(int i = 1; i <= n; i++)
if (!dfn[i]) tarjan(i, 0);
for(int i = 1; i <= n; i++)
if (!b[i]) cnt++, dfs(i);
//~~~
return 0;
}
有向圖
有向圖的強聯通分量
- 在一個強聯通分量中,存在x到y的路徑,就存在y到x的路徑
Code
void tarjan(int x) {
dfn[x] = low[x] = ++dfcnt;
s[++top] = x;
for(int i = head[x]; i; i = e[i].next) {
int y = e[i].t;
if (!dfn[y]) tarjan(y), low[x] = min(low[x], low[y]);
else if (!b[y]) low[x] = min(low[x], dfn[y]);
}
if (dfn[x] == low[x]) {
cnt++;
while(1) {
int y = s[top--];
b[y] = cnt;
size[cnt]++;
if (x == y) break;
}
}
}
例題
- luoguP3387縮點
Code
#include <queue>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1e4+5, M = 1e5+5;
struct side { int t, next; } e[M][2];
int head[N][2], tot[2];
void add(int x, int y, int k) {
e[++tot[k]][k].next = head[x][k];
head[x][k] = tot[k];
e[tot[k]][k].t = y;
}
int n, m, w[N], r[N], d[N], ans;
int dfn[N], low[N], dfcnt, sta[N], top, cnt, bel[N], sum[N];
void tarjan(int x) {
dfn[x] = low[x] = ++dfcnt;
sta[++top] = x;
for (int i = head[x][0]; i; i = e[i][0].next) {
int y = e[i][0].t;
if (!dfn[y]) tarjan(y), low[x] = min(low[x], low[y]);
else if (!bel[y]) low[x] = min(low[x], dfn[y]);
}
if (dfn[x] == low[x]) {
cnt++;
while (1) {
int y = sta[top--];
bel[y] = cnt;
sum[cnt] += w[y];
if (x == y) break;
}
}
}
queue<int> q;
int tuopu() {
for (int i = 1; i <= cnt; i++)
if (!r[i]) q.push(i), d[i] = sum[i];
while (!q.empty()) {
int x = q.front(); q.pop();
for (int i = head[x][1]; i; i = e[i][1].next) {
int y = e[i][1].t;
d[y] = max(d[y], d[x] + sum[y]);
if (--r[y] == 0) q.push(y);
}
}
for (int i = 1; i <= cnt; i++)
ans = max(ans, d[i]);
return ans;
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++)
scanf("%d", &w[i]);
for (int i = 1; i <= m; i++) {
int x, y;
scanf("%d%d", &x, &y);
add(x, y, 0);
}
for (int i = 1; i <= n; i++)
if (!dfn[i]) tarjan(i);
for (int x = 1; x <= n; x++)
for (int i = head[x][0]; i; i = e[i][0].next) {
int y = e[i][0].t;
if (bel[x] != bel[y])
r[bel[y]]++, add(bel[x], bel[y], 1);
}
printf("%d\n", tuopu());
return 0;
}