这几题练习强连通分量的Tarjan求法:
在一个有向图中,如果两个点a,b之间存在a->b的路径以及b->a的路径,则称a与b在同一强联通分量(SCC,strongly connected component)之中。因此可以将图划分为几个子图,每一个子图中都是一个极大强联通分量。如果将所有的强连通分量都缩成一个点,原图就变成了一个DAG(有向无环图)。
可以用Tarjan算法、Kosaraju算法、Gabow算法等来求图的强联通分量,其中的Tarjan算法在求图的割点、割边等方面也有很广泛的应用。
Tarjan算法是基于深度优先搜索的,只需要一次深搜和一个栈即可求出有向图的SCC。
在DFS过程中,每当遍历到一个节点,就将其压栈,同时为每个点都标记颜色,初始所有的点为白色,正在DFS的点为灰色,已经搜索完成的点为黑色。同时用dfn数组记录每个点的时间戳(即搜索的次序)。下面给出low数组的定义:
初始:dfn[u] = low[u],如果有边u->v,则:
第一题:Hoj 1520 The Bottom of a Graph
链接:http://acm.hit.edu.cn/hoj/problem/view?id=1520
求强联通分量后缩点,求出度为0的点。
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <vector>
#include <queue>
#include <stack>
#include <algorithm>
using namespace std;
#define Maxn 5005
#define Maxm 50005
struct Edge
{
int a,b;
}edge[Maxm];
int first[Maxn];
int next[Maxm];
int total;
int sccno[Maxn];
bool instack[Maxn];
int dfn[Maxn];
int low[Maxn];
int dfs_clock;
int scc_cnt;
int in[Maxn],out[Maxn];
stack <int> st;
void addEdge(int a,int b)
{
total++;
edge[total].a = a;edge[total].b = b;
next[total] = first[a];
first[a] = total;
}
void tarjan(int u)
{
dfn[u] = low[u] = ++dfs_clock;
st.push(u);
instack[u] = true;
for(int i=first[u];i!=-1;i=next[i])
{
int v = edge[i].b;
if(!dfn[v])
{
tarjan(v);
low[u] = min(low[u],low[v]);
}
else if(instack[v])
{
low[u] = min(low[u],dfn[v]);
}
}
if(dfn[u] == low[u])
{
scc_cnt++;
while(1)
{
int v = st.top();
st.pop();
instack[v] = false;
sccno[v] = scc_cnt;
if(u == v) break;
}
}
}
void find_scc(int n)
{
scc_cnt = dfs_clock = 0;
memset(dfn,0,sizeof(dfn));
memset(low,0,sizeof(low));
memset(instack,false,sizeof(instack));
while(!st.empty())
{
st.pop();
}
for(int i=1;i<=n;i++)
{
if(!dfn[i]) tarjan(i);
}
}
void solve(int n)
{
find_scc(n);
for(int i=1;i<=n;i++)
{
for(int j=first[i];j!=-1;j=next[j])
{
if(sccno[i]!=sccno[edge[j].b])
{
out[sccno[i]]++;
in[sccno[edge[j].b]]++;
}
}
}
vector <int> temp;
for(int i=1;i<=n;i++)
{
if(out[sccno[i]] == 0) temp.push_back(i);
}
for(int i=0;i<temp.size();i++)
{
if(i == 0) printf("%d",temp[i]);
else printf(" %d",temp[i]);
}
puts("");
}
void init()
{
memset(first,-1,sizeof(first));
memset(next,-1,sizeof(next));
memset(in,0,sizeof(in));
memset(out,0,sizeof(out));
total = 0;
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
#endif
int n,m;
int a,b;
while(scanf(" %d",&n)!=EOF && n!=0)
{
scanf(" %d",&m);
init();
for(int i=0;i<m;i++)
{
scanf(" %d %d",&a,&b);
addEdge(a,b);
}
solve(n);
}
return 0;
}
第二题:Poj 2186 Popular Cows
题目链接:http://poj.org/problem?id=2186
和上题类似,强连通分量所点后求出度为0的点,但是此题只能有一个出度为0的强联通。
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <vector>
#include <queue>
#include <stack>
#include <algorithm>
using namespace std;
#define Maxn 10005
#define Maxm 50005
struct Edge
{
int a,b;
}edge[Maxm];
int first[Maxn];
int next[Maxm];
int total;
int sccno[Maxn];
bool instack[Maxn];
int dfn[Maxn];
int low[Maxn];
int dfs_clock;
int scc_cnt;
int in[Maxn],out[Maxn];
stack <int> st;
void addEdge(int a,int b)
{
total++;
edge[total].a = a;edge[total].b = b;
next[total] = first[a];
first[a] = total;
}
void tarjan(int u)
{
dfn[u] = low[u] = ++dfs_clock;
st.push(u);
instack[u] = true;
for(int i=first[u];i!=-1;i=next[i])
{
int v = edge[i].b;
if(!dfn[v])
{
tarjan(v);
low[u] = min(low[u],low[v]);
}
else if(instack[v])
{
low[u] = min(low[u],dfn[v]);
}
}
if(dfn[u] == low[u])
{
scc_cnt++;
while(1)
{
int v = st.top();
st.pop();
instack[v] = false;
sccno[v] = scc_cnt;
if(u == v) break;
}
}
}
void find_scc(int n)
{
scc_cnt = dfs_clock = 0;
memset(dfn,0,sizeof(dfn));
memset(low,0,sizeof(low));
memset(instack,false,sizeof(instack));
while(!st.empty())
{
st.pop();
}
for(int i=1;i<=n;i++)
{
if(!dfn[i]) tarjan(i);
}
}
int solve(int n)
{
find_scc(n);
for(int i=1;i<=n;i++)
{
for(int j=first[i];j!=-1;j=next[j])
{
if(sccno[i]!=sccno[edge[j].b])
{
out[sccno[i]]++;
in[sccno[edge[j].b]]++;
}
}
}
int ans = 0;
int bottomNum = 0;
int flag = 0;
for(int i=1;i<=n;i++)
{
if(out[sccno[i]] == 0)
{
ans ++;
if(flag == 0)
{
bottomNum = sccno[i];
flag = 1;
}
else
{
if(bottomNum!=sccno[i]) return 0;
}
}
}
return ans;
}
void init()
{
memset(first,-1,sizeof(first));
memset(next,-1,sizeof(next));
memset(in,0,sizeof(in));
memset(out,0,sizeof(out));
total = 0;
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
#endif
int n,m;
int a,b;
while(scanf(" %d %d",&n,&m)!=EOF)
{
init();
for(int i=0;i<m;i++)
{
scanf(" %d %d",&a,&b);
addEdge(a,b);
}
int ans = solve(n);
printf("%d\n", ans);
}
return 0;
}
第三题:Poj 1904 King's Quest
题目链接:http://poj.org/problem?id=1904
先把男上向喜欢的女生连线,然后在给出的一组可行解中。女生向对应的男生连线。
求此有向图的强连通分量即可。
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <vector>
#include <queue>
#include <stack>
#include <algorithm>
using namespace std;
#define Maxn 4005
#define Maxm 300005
struct Edge
{
int a,b;
}edge[Maxm];
int first[Maxn];
int next[Maxm];
int total;
int sccno[Maxn];
bool instack[Maxn];
int dfn[Maxn];
int low[Maxn];
int dfs_clock;
int scc_cnt;
stack <int> st;
void addEdge(int a,int b)
{
total++;
edge[total].a = a;edge[total].b = b;
next[total] = first[a];
first[a] = total;
}
void tarjan(int u)
{
dfn[u] = low[u] = ++dfs_clock;
st.push(u);
instack[u] = true;
for(int i=first[u];i!=-1;i=next[i])
{
int v = edge[i].b;
if(!dfn[v])
{
tarjan(v);
low[u] = min(low[u],low[v]);
}
else if(instack[v])
{
low[u] = min(low[u],dfn[v]);
}
}
if(dfn[u] == low[u])
{
scc_cnt++;
while(1)
{
int v = st.top();
st.pop();
instack[v] = false;
sccno[v] = scc_cnt;
if(u == v) break;
}
}
}
void find_scc(int n)
{
scc_cnt = dfs_clock = 0;
memset(dfn,0,sizeof(dfn));
memset(low,0,sizeof(low));
memset(instack,false,sizeof(instack));
while(!st.empty())
{
st.pop();
}
for(int i=1;i<=n;i++)
{
if(!dfn[i]) tarjan(i);
}
}
int solve(int n)
{
find_scc(n);
return 0;
}
void init()
{
memset(first,-1,sizeof(first));
memset(next,-1,sizeof(next));
total = 0;
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
#endif
int n;
int num;
int b;
init();
while(scanf(" %d",&n)!=EOF)
{
for(int a=1;a<=n;a++)
{
scanf(" %d",&num);
for(int j=0;j<num;j++)
{
scanf(" %d",&b);
addEdge(a,b+n);
}
}
for(int a=1;a<=n;a++)
{
scanf(" %d",&b);
addEdge(b+n,a);
}
solve(2*n);
vector <int> temp;
for(int i=1;i<=n;i++)
{
temp.clear();
for(int j=first[i];j!=-1;j=next[j])
{
int v = edge[j].b;
if(sccno[i] == sccno[v]) temp.push_back(v-n);
}
sort(temp.begin(),temp.end());
printf("%d",temp.size());
for(int j=0;j<temp.size();j++)
{
printf(" %d",temp[j]);
}
puts("");
}
}
return 0;
}