有向图的强连通分量(tarjan算法)
2016-05-08 20:10
260 查看
强连通分量
有向图强连通分量:在有向图G中,如果两个顶点vi,vj间(vi>vj)有一条从vi到vj的有向路径,同时还有一条从vj到vi的有向路径,则称两个顶点强连通(stronglyconnected)。如果有向图G的每两个顶点都强连通,称G是一个强连通图。有向图的极大强连通子图,称为强连通分量(strongly connected
components)。
考虑强连通分量C,设其中第一个被发现的点为x,则,C中其他的点都是x的后代。我们希望在x访问完成时立即输出C(可以同时记录C,输出代表当前在当前的遍历序列中剔除),这样就可以在同一颗DFS树种区分开所有SCC了,因此问题的关键是判断一个点是否为一个SCC中最先被发现的点。
贴一个算法解析http://blog.csdn.net/wsniyufang/article/details/6604458
SDUT3262
利用targan算法求出图中所有的强连通分量,将相同连通分量的点缩成一个点,然后重新构图,BFS求最短路即可。
#include<cstdio> #include<algorithm> #include<vector> #include<iostream> #include<cstring> #include<queue> #include<stack> #include<map> #include<set> #define INF 0x3f3f3f3f #define mem(a,x) memset(a,x,sizeof(a)) using namespace std; typedef long long ll; const int N = 100010; int low ;//当前能回溯到的栈中最小的次序号 int pre ; // 记录当前节点的次序号。(时间戳) int scc ;//记录节点所属强连通分量 stack<int>st;//栈中储存当前未处理的节点(访问了,但并没有划分为连通分量) int dfs_num;//次序号 int cnt;//强连通分量的编号 1 ~ maxNumberOfSCC int n,m; int dis ; vector<int>V ,G ; void init(){ mem(low,0);mem(pre,0);mem(scc,0); dfs_num = 0; cnt = 0; while(!st.empty()) st.pop(); for(int i=0;i<=n;i++){ V[i].clear();G[i].clear(); } } void tarjan(int x){ pre[x] = low[x] = ++dfs_num; st.push(x); for(int i=0;i<V[x].size();i++){ int v = V[x][i]; if(!pre[v]){//没有访问过 tarjan(v); low[x] = min(low[x],low[v]); } else if(!scc[v]){// 访问过了,但是没有划分联通分量, //也就是在栈中 low[x] = min(low[x],pre[v]); // pre[v],v节点的时间戳 } } if(low[x] == pre[x] ){//当前的次序号等于 能回溯到的最小次序号 //说明找到了”根“节点 cnt++; while(1){ int tmp = st.top(); st.pop(); scc[tmp] = cnt; if(tmp == x) break; } } } int bfs(int x){ queue<int>Q; Q.push(x); mem(dis,-1); dis[x] = 0; while(!Q.empty()){ int u = Q.front();Q.pop(); for(int i=0;i<G[u].size();i++){ int v = G[u][i]; if(dis[v] == -1){ dis[v] = dis[u] + 1; // cout<<dis[v]<<" sdsd"<<endl; if(v == scc[n-1]) return dis[v]; Q.push(v); } } } return dis[scc[n-1]]; } int main(){ int t; scanf("%d",&t); while(t--){ scanf("%d%d",&n,&m); init(); int a,b; for(int i=0;i<m;i++){ scanf("%d%d",&a,&b); V[a].push_back(b); } for(int i=0;i<n;i++){ if(!pre[i]) tarjan(i);//缩点 } // for(int i=0;i<n;i++) // cout<<scc[i]<<endl; for(int i=0;i<n;i++){ //重新构图 for(int j=0;j<V[i].size();j++){ int v = V[i][j]; if(scc[i] != scc[v]) G[scc[i]].push_back(scc[v]); } } int ans = bfs(scc[0]); printf("%d\n",ans); } }
HDU1269
Tarjan 裸题
直接求强连通分量,连通分量数为1即输出Yes(强连通图)。
POJ1236
盗图 ,思路参考http://blog.csdn.net/sr_19930829/article/details/39554371
英语太烂,题目没读太懂。。
建图,求强连通缩点,出来新图DAG,然后找所有入度为0的顶点,即为问题一的答案。(显然)
有这么一个定理,对于一个DAG(Directed Acyclic Graph),设所有入度为0的顶点数为n,所有出度为0的顶点数为m,则至少加 max(n,m)条边可形成一个强连通分量。
坑点,当给定的图是一个强连通图,即只有一个强连通分量时,不能用求答案2的方法去求 加多少条边,因为显然不用加边了。
#include<cstdio> #include<algorithm> #include<vector> #include<iostream> #include<cstring> #include<queue> #include<stack> #include<map> #include<set> #define INF 0x3f3f3f3f #define mem(a,x) memset(a,x,sizeof(a)) using namespace std; typedef long long ll; const int maxn = 100 + 10; vector<int>G[110]; vector<int>V[110]; stack<int>st; int pre[maxn]; int low[maxn]; int scc[maxn]; int dfs_num;int cnt; void init(){ mem(pre,0); mem(low,0); mem(scc,0); dfs_num = 0,cnt = 0; while(!st.empty()) st.pop(); for(int i=1;i<=100;i++){ V[i].clear();G[i].clear(); } } void tarjan(int x){ pre[x] = low[x] = ++dfs_num; st.push(x); for(int i=0;i<V[x].size();i++){ int v = V[x][i]; if(!pre[v]){ tarjan(v); low[x] = min(low[x],low[v]); } else if(!scc[v]){ low[x] = min(low[x],pre[v]); } } if(low[x] == pre[x]){ cnt++; while(1){ int tmp = st.top();st.pop(); scc[tmp] = cnt; if(tmp == x) break; } } } int main(){ int n; scanf("%d",&n); init(); int tmp; for(int i=1;i<=n;i++){ while(scanf("%d",&tmp)&& tmp){ V[i].push_back(tmp); } } for(int i=1;i<=n;i++){ if(!pre[i]) tarjan(i); } //for(int i=1;i<=n;i++) // printf("%d\n",scc[i]); if(cnt == 1){ printf("1\n0\n"); return 0; } for(int i=1;i<=n;i++){ for(int j=0;j<V[i].size();j++){ int v = V[i][j]; if(scc[i] != scc[v]){ G[scc[i]].push_back(scc[v]); } } } int zeroOutDegree = 0; int zeroInDegree = 0; int indegree[110]; mem(indegree,0); for(int i=1;i<=cnt;i++){ if(G[i].size() == 0) zeroOutDegree++; for(int j=0;j<G[i].size();j++){ int v = G[i][j]; indegree[v]++; } } for(int i=1;i<=cnt;i++){ if(indegree[i] == 0) zeroInDegree++; } printf("%d\n",zeroInDegree); printf("%d\n",max(zeroInDegree,zeroOutDegree)); return 0; }
POJ2186
题目大意,有很多牛和牛的关系(A,B)代表A认为B受欢迎,(B,C)代表B认为C受欢迎,这种关系可以传递,所以A也认为C受欢迎。
给出这些关系,问一共有多少牛受所有牛的欢迎。
1.求出所有的强连通分量,用tarjan算法 2.每个强连通分量缩成一点,则形成一个有向无环图DAG。 3.DAG上面如果有唯一的出度为0的点,则改点能被所有的点可达。 那么该点所代表的连通分量上的所有的原图中的点,都能被原图中 的所有点可达 ,则该连通分量的点数就是答案。 4.DAG上面如果有不止一个出度为0的点,则这些点互相不可达,原问题 无解,答案为0; by kuangbin
想出DAG中存在唯一出度为0的点可以被所有点可达就OK了
#include<cstdio>
#include<algorithm>
#include<vector>
#include<iostream>
#include<cstring>
#include<queue>
#include<stack>
#include<map>
#include<set>
#define INF 0x3f3f3f3f
#define mem(a,x) memset(a,x,sizeof(a))
using namespace std;
typedef long long ll;
const int N = 10010;
int low
;//当前能回溯到的栈中最小的次序号
int pre
; // 记录当前节点的次序号。(时间戳)
int scc
;//记录节点所属强连通分量
stack<int>st;//栈中储存当前未处理的节点(访问了,但并没有划分为连通分量)
int dfs_num;//次序号
int cnt;//强连通分量的编号 1 ~ maxNumberOfSCC
int n,m;
int dis
;
int numOfscc
;
vector<int>V
,G
;
void init(){
mem(low,0);mem(pre,0);mem(scc,0);mem(numOfscc,0);
dfs_num = 0; cnt = 0;
while(!st.empty()) st.pop();
for(int i=0;i<=n;i++){
V[i].clear();G[i].clear();
}
}
void tarjan(int x){
pre[x] = low[x] = ++dfs_num;
st.push(x);
for(int i=0;i<V[x].size();i++){
int v = V[x][i];
if(!pre[v]){//没有访问过
tarjan(v);
low[x] = min(low[x],low[v]);
}
else if(!scc[v]){// 访问过了,但是没有划分联通分量,
//也就是在栈中
low[x] = min(low[x],pre[v]); // pre[v],v节点的时间戳
}
}
if(low[x] == pre[x] ){//当前的次序号等于 能回溯到的最小次序号
//说明找到了”根“节点
cnt++;
int l = 0;
while(1){
int tmp = st.top(); st.pop();
scc[tmp] = cnt; l++;
if(tmp == x) break;
}
numOfscc[cnt] = l;
}
}
int bfs(int x){
queue<int>Q;
Q.push(x);
mem(dis,-1);
dis[x] = 0;
while(!Q.empty()){
int u = Q.front();Q.pop();
for(int i=0;i<G[u].size();i++){
int v = G[u][i];
if(dis[v] == -1){
dis[v] = dis[u] + 1;
if(v == scc[n-1]) return dis[v];
Q.push(v);
}
}
}
return dis[scc[n-1]];
}
int main(){
int t;
while(scanf("%d%d",&n,&m)!=EOF){
init();
int a,b;
for(int i=0;i<m;i++){
scanf("%d%d",&a,&b);
V[a].push_back(b);
}
for(int i=1;i<=n;i++){
if(!pre[i])
tarjan(i);//缩点
}
int ans = 0;
int indegree
;mem(indegree,0);
//cout<<cnt<<endl;
for(int i=1;i<=n;i++){ //重新构图
for(int j=0;j<V[i].size();j++){
int v = V[i][j];
if(scc[i] != scc[v]){
G[scc[i]].push_back(scc[v]);
}
}
}
int l = 0;
for(int i=1;i<=cnt;i++){
if(G[i].size() == 0){
l++; ans = numOfscc[i];
}
}
if(l == 1)
printf("%d\n",ans);
else
printf("0\n");
}
}
相关文章推荐
- 初学图论-Kahn拓扑排序算法(Kahn's Topological Sort Algorithm)
- 初学图论-Bellman-Ford单源最短路径算法
- 初学图论-DAG单源最短路径算法
- 初学图论-Dijkstra单源最短路径算法
- 初学图论-Dijkstra单源最短路径算法基于优先级队列(Priority Queue)的实现
- 封装好的Folyd建图,C++源码
- LCA模板
- 图论学习笔记之一——Floyd算法
- 【LCA】SPOJ QTREE2
- poj 3249 Test for Job 最长路
- HDU 2544
- Timus 1557 Network Attack DFS+各种各种...
- HDU1289 Tarjan-模板题
- Poj2638 网络流+最短路+二分答案
- Aizu1311 分层图最短路 (...大概)
- HDU 3631 Shortest Path
- 二分图匹配模板
- 最短路径 -- spfa
- POJ2377 Bad Cowtractors
- Six Degrees of Cowvin Bacon(最短路径floyd算法)