TARJAN
前置知识
- 强连通:对于两点u, v,从u到v和从v到u都有路径,则称u和v强连通
- 强联通图:强连通图(Strongly Connected Graph)是指在有向图G中,如果对于任意两点u, v,从u到v和从v到u都有路径,则称G是强连通图
- 极大强连通子图:一个有向图中无法再加一个点的强连通子图
- 强连通分量(scc):一个有向图的极大强连通子图
- 领接表存图
- dfs
解决的问题
求图的强连通分量
通过缩点将一个有向图转化为一个DAG
思想
通过dfs的过程中搜索顺序和边的特点进行判断点之间的关系(时间戳)
过程
1.一个栈
将dfs搜到的点存在一个栈中
2.两个数组
dfn[ ], low[ ]的定义:
dfn[u]为 dfs 此有向图过程中搜到u的顺序(时间戳) ,
low[u] 为u在当前栈中能到的点中dfn最小值
3.四种dfs搜索边
树枝边:指向dfs搜索树中还未遍历到的点的边(指向子树中未遍历过得节点)
前向边:指向子树中遍历过得节点的边
后向边:指向栈中祖先的边
横插边:其他边
4.DFS
5.判断low[now]的值
若当前搜到一条树枝边,则用指向点的low值更新
若当前搜到一条前向或后向边,则用指向点的dfn值更新//祖先的low未算好且对答案无影响
若当前搜到一条横插边,则忽略(无影响)
6.求scc(scccnt,sccnum[ ])
若当前节点now的dfn和low值相同,则now为当前求的scc中最深度最小的点(栈中now后的点为scc中的点)
将栈中now节点之后的点出栈,标记为一个scc
7.CODE
int dfn[MAXN], low[MAXN], ind;
int s[MAXN], stp;//栈
int scccnt, sccnum[MAXN], sccsize[MAXN];
void tarjan(int now) {
dfn[now] = low[now] = ++ind;
s[stp++] = now;//stp存的是栈顶的上一个位置
for (int i = head[now]; i ; i = nxt[i]) {
if(!dfn[v[i]]) {
tarjan(v[i]);
low[now] = min(low[v[i]], low[now]);//树枝边
}
else if(!sccnum[v[i]]) {
low[now] = min(dfn[v[i]], low[now]);//前后向边
}
}
if(dfn[now] == low[now]) { // 分割点
scccnt++;
while(s[stp] != now) {
sccnum[s[--stp]] = scccnt;
sccsize[scccnt]++;
}
}
return;
}
常见模型
- [HAOI2006]受欢迎的牛
//题意为求一个图中唯一的出度为0的强联通
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 100001;
const int MAXM = 50001;
int n, m;
int head[MAXN], cnt, nxt[MAXM], v[MAXM];
void add(int x, int y) {
nxt[++cnt] = head[x];
head[x] = cnt;
v[cnt] = y;
}
int dfn[MAXN], low[MAXN], ind;//dfn[u] : dfs图过程中搜到u的顺序 ,low[u] : u和u可到的子树中dfn的最小值
int s[MAXN], stp;//栈
int scccnt, sccnum[MAXN], sccsize[MAXN];
int out[MAXN];//每个强联通的出度
int ans = 0;
void tarjan(int now) {
dfn[now] = low[now] = ++ind;
s[stp++] = now;//stp存的是栈顶的上一个位置
for (int i = head[now]; i ; i = nxt[i]) {
if(!dfn[v[i]]) {
tarjan(v[i]);
low[now] = min(low[v[i]], low[now]);//树枝边
}
else if(!sccnum[v[i]]) {
low[now] = min(dfn[v[i]], low[now]);//前后向边
}
}
if(dfn[now] == low[now]) { // 分割点
scccnt++;
while(s[stp] != now) {
sccnum[s[--stp]] = scccnt;
sccsize[scccnt]++;
}
}
return;
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= m; i++) {
int x, y;
scanf("%d%d", &x, &y);
add(x, y);
}
for (int i = 1; i <= n; i++) {
if(!dfn[i]) tarjan(i);
}
//for (int i = 1; i <= n; i++) printf("dfn[%d] = %d, low[%d] = %d, sccnum[%d] = %d
", i, dfn[i], i, low[i], i, sccnum[i]);
//求唯一的出度为0的强联通
for (int i = 1; i <= n; i++) {
for (int j = head[i]; j ; j = nxt[j]) {
if(sccnum[i] != sccnum[v[j]])
out[sccnum[i]]++;
}
}
bool flag = 0;
for (int i = 1; i <= scccnt; i++) {
if(flag == 1 && out[i] == 0) {
printf("0");
return 0;
}
else if(out[i] == 0) {
ans = sccsize[i];
flag = 1;
}
}
printf("%d", ans);
return 0;
}
- [USACO06JAN]牛的舞会The Cow Prom
//题意为求点数大于1的scc的个数
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 10001;
const int MAXM = 50001;
int n, m;
int head[MAXN], cnt, nxt[MAXM], v[MAXM];
void add(int x, int y) {
nxt[++cnt] = head[x];
head[x] = cnt;
v[cnt] = y;
}
int s[MAXN], stp;
int dfn[MAXN], low[MAXN], ind;
int sccnum[MAXN], scccnt, sccsize[MAXN];
void tarjan(int now) {
dfn[now] = low[now] = ++ind;
s[stp++] = now;
for (int i = head[now]; i ; i = nxt[i]) {
if(!dfn[v[i]]) {
tarjan(v[i]);
low[now] = min(low[now], low[v[i]]);
}
else if(!sccnum[v[i]]) {
low[now] = min(low[now], dfn[v[i]]);
}
}
if(dfn[now] == low[now]) {
scccnt++;
while(s[stp] != now) {
sccnum[s[--stp]] = scccnt;
sccsize[scccnt]++;
}
}
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= m; i++) {
int x, y;
scanf("%d%d", &x, &y);
add(x, y);
}
for (int i = 1; i <= n; i++)
if(!dfn[i])
tarjan(i);
int ans = 0;
for (int i = 1; i <= scccnt; i++)
if(sccsize[i] > 1)
ans++;
printf("%d", ans);
return 0;
}