您的位置:首页 > 其它

有向图的强联通分量

2014-02-10 19:47 169 查看
做到了有向图的强联通分量,无意中看到了川哥写的总结感觉很好,也学习一下写一下、、先写一篇日后再继续补充。。。。

大牛博客:https://www.byvoid.com/blog/scc-tarjan/

还有川哥写的:http://www.toposort.com/blog/strongly-connected-component.html

显然还有刘汝佳写的大白书P321.。

在有向图G中,如果两个顶点间至少存在一条路径,称两个顶点强连通(strongly
connected)。如果有向图G的每两个顶点都强连通,称G是一个强连通图。非强连通图有向图的极大强连通子图,称为强连通分量(strongly
connected components)。

摘一段大白书上写的:考虑到强联通分量C,设其中第一个被发现的点为x,则C中其他点都是x的后代。我们希望在x访问完成时立刻输出C。这样,就可以在一棵DFS树中区分开所有SCC(强联通分量)了。因此问题的关键,是判断一个点是否为一个SCC中最先被发现的点。

我感觉找强联通分量就是一开始把每一个点压入栈内,通过dfs的过程找到他们分别属于的不同的强联通分量,然后当一个变量到达它的“时间”和他所属的分量当中最小的“时间”相同时,说名这个点所连接起来的这一个回路上的所有的点就是在一个分量中。这里感觉有点类似于块的划分似得,标记到不同的分量当中。

下面是一些联系训练:

1.POJ 2186 

Popular Cows

Time Limit: 2000MS Memory Limit: 65536K
Total Submissions: 21434 Accepted: 8757
Description

Every cow's dream is to become the most popular cow in the herd. In a herd of N (1 <= N <= 10,000) cows, you are given up to M (1 <= M <= 50,000) ordered pairs of the form (A, B) that tell you that cow A thinks that cow B is popular. Since popularity is transitive,
if A thinks B is popular and B thinks C is popular, then A will also think that C is 

popular, even if this is not explicitly specified by an ordered pair in the input. Your task is to compute the number of cows that are considered popular by every other cow. 

Input

* Line 1: Two space-separated integers, N and M 

* Lines 2..1+M: Two space-separated numbers A and B, meaning that A thinks B is popular. 

Output

* Line 1: A single integer that is the number of cows who are considered popular by every other cow. 

Sample Input
3 3
1 2
2 1
2 3

Sample Output
1

题目简述:n头奶牛,给出若干个欢迎关系a b,表示a欢迎b,欢迎关系是单向的,但是是可以传递的。另外每个奶牛都是欢迎他自己的。求出被所有的奶牛欢迎的奶牛的数目。
题目分析来自:http://www.cppblog.com/RyanWang/archive/2009/02/26/74984.aspx解释的很好。
模型转换:N个顶点的有向图,有M条边(N≤10000,M≤50000)。求一共有多少个点,满足这样的条件:所有其它的点都可以到达这个点。

首先,这个题的N和M都非常大,硬做是肯定不行的。考虑如果这个图是一棵树,那么问题就变的很简单了,因为至多有一个点满足条件,这个点满足条件的充要条件是:这个点是树中唯一的出度为0的点。

那么我们能否把图转化为树 有向无回路图DAG(感谢网友提出)呢?首先可以想到的是,如果图中包含有环,那么就可以把这个环缩成一个点,因为环中的任意两个点可以到达,环中所有的点具有相同的性质,即它们分别能到达的点集都是相同的,能够到达它们的点集也是相同的。那么是否只有环中的点才具有相同的性质呢?进一步的考虑,图中的每一个极大强连通分支中的点都具有相同的性质。所以,如果把图中的所有极大强连通分支求出后,就可以把图收缩成一棵树  DAG,问题就迎刃而解了。 

预备知识:有向图的强连通分量的求法,这个和求割点的算法差不多。

算法框架:对有向图求强连通分量,然后找出所有独立的强连通分量(所谓独立,就是该连通分量里面的点到外面的点没有通路,当然,连通分量外的点是可以有路到强连通分量内的点的),如果独立的强连通分量的数目只有一个,那么,就输出这个强连通分量内解的个数,否则输出无解。

算法证明:

1:假设a和b都是最受欢迎的cow,那么,a欢迎b,而且b欢迎a,于是,a和b是属于同一个连通分量内的点,所有,问题的解集构成一个强连通分量。

2:如果某个强连通分量内的点a到强连通分量外的点b有通路,因为b和a不是同一个强连通分量内的点,所以b到a一定没有通路,那么a不被b欢迎,于是a所在的连通分量一定不是解集的那个连通分量。

3:如果存在两个独立的强连通分量a和b,那么a内的点和b内的点一定不能互相到达,那么,无论是a还是b都不是解集的那个连通分量,问题保证无解。

4:如果图非连通,那么,至少存在两个独立的连通分量,问题一定无解。

#include <algorithm>
#include <iostream>
#include <stdlib.h>
#include <string.h>
#include <iomanip>
#include <stdio.h>
#include <string>
#include <queue>
#include <cmath>
#include <stack>
#include <map>
#include <set>
#define eps 1e-7
#define M 1000100
#define LL __int64
#define INF 0x3f3f3f3f
#define PI 3.1415926535898

const int maxn = 1000100;
using namespace std;

struct node
{
int u, v;
int next;
} f[maxn];

stack<int> q;
int head[maxn];
int low[maxn];//图中的最小时间戳
int dfn[maxn];//到达这一步的的时候的时间戳
int isb[maxn];
int n, m, nums;
int t, dfs_clock;
int out[maxn];
int num[maxn];

void init()
{
t = 0;
memset(head, -1 , sizeof(head));
memset(num, 0 , sizeof(num));
nums = 0;
memset(low, 0 , sizeof(low));
memset(dfn, 0 , sizeof(dfn));
dfs_clock = 0;
}
void add(int x, int y)
{
f[t].u = x;
f[t].v = y;
f[t].next = head[x];
head[x] = t++;
}

void tarjan(int u)
{
dfn[u] = low[u] = ++dfs_clock;
q.push(u);
for(int i = head[u]; i != -1; i = f[i].next)
{
int v = f[i].v;
if(!dfn[v])
{
tarjan(v);
low[u] = min(low[u], low[v]);
}
else if(!num[v])
low[u] = min(low[u], dfn[v]);
}
if(low[u] == dfn[u])
{
int v = -1;
nums ++;
while(u != v)
{
v = q.top();
q.pop();
num[v] = nums;
}
}
}

int main()
{
int i, j;
while(cin >>n>>m)
{
int u, v;
init();
for(i = 0; i < m; i++)
{
cin >>u>>v;
add(u, v);
}
for(i = 1; i <= n; i++)
if(!dfn[i])
tarjan(i);
for(i = 1; i <= n; i++)
{
for(j = head[i]; j != -1; j = f[j].next)
{
v = f[j].v;
if(num[i] != num[v])
{
out[num[i]]++;
}
}
}
int sum = 0;
int ans = 0;
for(i = 1; i <= nums; i++)
{
if(!out[i])
{
sum ++;//统计强联通分量的个数
ans = i;
}
}
if(sum != 1)
cout<<"0"<<endl;
else
{
int cnt = 0;
for(i = 1; i <= n; i++)
{
if(num[i] == ans)
cnt++;
}
cout<<cnt<<endl;
}
}
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  ACM poj