您的位置:首页 > 运维架构

POJ 2186-Popular Cows:强连通分量问题

2013-07-21 02:02 295 查看
点击打开链接

Popular Cows

Time Limit: 2000MS Memory Limit: 65536K
Total Submissions: 19665 Accepted: 7970
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

Hint

Cow 3 is the only cow of high popularity. 

第一次做联通分量的题,大致的步骤明白了,可是写出来的代码效率并不如别人用同样算法写的高,我用的是kosaraju算法:
先全图DFS一遍,深搜不下去就换没搜的点继续,直到全部点都搜过,给每个点编一个号,可以是前序遍历的序号,也可以是后续遍历的序号,反正后面要用到.

将全图的边倒置.

在做一次全图的DFS,选择点的顺序跟之前编号的顺序有关,如果是前序,就按号码从小到大选点,如果是后序,就按由大到小选点,反正这次选点的顺序跟之前的顺序保持一致(比如第一次先选的A,然后A->B, B->C,那么第二次遍历的顺序也要先从A开始),这样搜索到的每个节点都和根节点是在同一个强连通分量里,同时可以给没一个强连通分量分配一个标记值,一般理解成颜色,同一个颜色是一个强连通分量,,然后每个强连通分量可以看成一个点;

然后还要遍历所有的点,记录下出度为0的颜色,如果出度为0的颜色大于一个,那么这几个颜色必然不联通,如果没有出度为0的颜色,那么全图联通,如果有一个这样的颜色,那么这个颜色代表的结点的个数就是所求的答案,还是图论的题做的太少,思考问题的方式比较死板,总是从点的角度出发,发现点的个数是1万,邻接矩阵超空间了,所以用的临街表,用时800MS,我看网上别人用这个算法只跑了200多,是从边的角度出发的,因为边最多只有5万,一个数组就表示了,还有更快的50MS就跑出来了,我也不知道怎么实现的,先附上我的AC代码,然后贴上别人的解析,再研究研究:

#include<stdio.h>
#include<string.h>
#include<vector>
using namespace std;

vector<int> map[10010][2];
int use[10010];
int time[10010];
int color[10010];
int num[10010];

int total;
int c;
void dfs1(int cow)
{
use[cow] = 1;
int i;
int len = map[cow][0].size();
for(i = 0; i < len; i++)
{
if(use[map[cow][0][i]] == 0)
{
dfs1(map[cow][0][i]);
}
}
time[total++] = cow;
}
void dfs2(int cow)
{
use[cow] = 1;
color[cow] = c;
num[c]++;
int i;
int len = map[cow][1].size();
for(i = 0; i < len; i++)
{
if(use[map[cow][1][i]] == 0)
{
use[map[cow][1][i]] = 1;
dfs2(map[cow][1][i]);
}
}
}
int main()
{
//	freopen("in.txt", "r", stdin);
int n, m;
while(scanf("%d%d", &n, &m) != EOF)
{
int i, f, t;
memset(use, 0, sizeof(use));
memset(map, 0, sizeof(map));
memset(time, 0, sizeof(time));
memset(color, 0, sizeof(color));
memset(num, 0, sizeof(num));
total = c = 0;
for(i = 0; i < m; i++)
{
scanf("%d%d", &f, &t);
map[f][0].push_back(t);
map[t][1].push_back(f);
}

for(i = 1; i <= n; i++)
{
if(use[i] == 0)
dfs1(i);
}
memset(use, 0, sizeof(use));
c = -1;
for(i = n - 1; i >= 0; i--)
{
if(use[time[i]] == 0)
{
c++;
dfs2(time[i]);
}
}
memset(use, 0, sizeof(use));
int j;
int len;
for(i = 1; i <= n; i++)
{
if(use[color[i]] == 1)
continue;
len = map[i][0].size();
for(j = 0; j < len; j++)
{
if(color[map[i][0][j]] != color[i])
{
use[color[i]] = 1;
}
}
}
int flag= 0;
int mark;
for(i = 0; i <= c; i++)
{
if(use[i] == 0)
{
flag++;
mark = i;
}
}
if(flag == 0)
printf("%d\n", n);
else if(flag == 1)
{
printf("%d\n", num[mark]);
}
else
printf("0\n");
}
return 0;
}


这是网上的代码:

pku 2186 Popular Cows 强连通分量 -> Kosaraju
题目大意:给定一个有向图,求有多少个顶点是由任何顶点出发都可达的。顶点数<= 10,000,边数 <= 50,000
代码:
#include<iostream>
using namespace std;
const int maxn=10005;
const int maxm=50005;
int n,m;

/*
* 有用的定理:
* 有向无环图中唯一出度为0的点,一定可以由任何点出发均可达
* (由于无环,所以从任何点出发往前走,必然终止于一个出度为0的点)
* 步骤:
* 1. 求出所有强连通分量
* 2. 每个强连通分量缩成一点,则形成一个有向无环图DAG。
* 3. DAG上面如果有唯一的出度为0的点,则该点能被所有的点可达。
* 那么该点所代表的连通分量上的所有的原图中的点,都能被原图中的所有点可达,则该连通分量的点数,就是答案。
* 4. DAG上面如果有不止一个出度为0的点,则这些点互相不可达,原问题无解,答案为0
* 技巧:
* 缩点的时候不一定要构造新图,只要把不同强连通分量的点染不同颜色,
* 然后考察各种颜色的点有没有连到别的颜色的边即可(即其对应的缩点后的DAG图上的点是否有出边)。
*/
struct node
{
int s,e;
int next;
}edg1[maxm],edg2[maxm];//保存原边和反边
int tot1,tot2;
int head1[maxn],head2[maxn];
void add(int a,int b)
{
tot1++;
edg1[tot1].s=a;
edg1[tot1].e=b;
edg1[tot1].next=head1[a];
head1[a]=tot1;
tot2++; //反边一起加了
edg2[tot2].s=b;
edg2[tot2].e=a;
edg2[tot2].next=head2[b];
head2[b]=tot2;
}

/*Kosaraju 强联通分量算法步骤
* begin
* 1.深度优先遍历G,算出每个结点u的结束时间f[u],起点如何选择无所谓。
* 2.深度优先遍历G的转置图GT,选择遍历的起点时,按照结点的结束时间从大到小进行。
* 遍历的过程中,一边遍历,一边给结点做分类标记,每找到一个新的起点,分类标记值就加1。
* 3. 第2步中产生的标记值相同的结点构成深度优先森林中的一棵树,也即一个强连通分量
* end;

*/
int cnt1,cnt2,num;
bool used[maxn];
int f[maxn],belong[maxn];//f[ i ] = u:时间 i 出栈的结点是 u ;belong[ i ]:i 属于哪个强联通分量
int color[maxn];//保存最后缩点后各个颜色(强连通分量)有多少个点
void dfs1(int x)//搜出一棵树,算出每个结点 u 的结束时间f[u]
{
used[x]=true;
for(int i=head1[x];i!=-1;i=edg1[i].next)
if(!used[edg1[i].e])
dfs1(edg1[i].e);
f[++cnt1] = x; //算出每个结点x的结束时间
}
void dfs2(int x)//深度优先遍历G的转置图GT
{
used[x]=true;
++num; //该强连通分量里有多少个节点
belong[x]=cnt2; //一边遍历,一边给结点做分类标记
for(int i=head2[x];i!=-1;i=edg2[i].next)
if(!used[edg2[i].e])
dfs2(edg2[i].e);
}
void Kosaraju()//求强连通分量,同时就缩点了 O(e+v)
{
for(int i=1;i<=n;i++)
used[i]=false;
cnt1=0;
for(int i=1;i<=n;i++)//为防孤立点等,搜出整个森林。起点如何选择无所谓。
if(!used[i])
dfs1(i);
for(int i=1;i<=n;i++)
used[i]=false;
cnt2=0;
for(int i=cnt1;i>=1;i--)//选择遍历的起点时,按照结点的结束时间从大到小进行
if(!used[ f[i] ])
{
num=0; ++cnt2;
/* 每找到一个新的起点,分类标号就加1* 产生的标记值相同的结点构成深度优先森林中的一棵树,也即一个强连通分量
*/
dfs2( f[i] );
color[ cnt2 ]=num;//缩点的时候不一定要构造新图,只要把不同强连通分量的点染不同颜色,
}
}
int du[maxn];//保存点的出度
void solve()
{
memset(du,0,sizeof(du));
for(int i=1;i<=m;i++)//虽然缩点了,但计算出度时还是必须遍历每一条边
{
if(belong[ edg1[i].s ] != belong[ edg1[i].e ])//如果不在同一个强连通分量内才算出度
du[ belong[ edg1[i].s] ]++;
}
int cnt=0,pos;
for(int i=1;i<=cnt2;i++)//遍历缩点后的每一个DAG图(强连通分量),检查出度
if(!du[i])
{
cnt++;
pos=i;
}
if(cnt != 1) puts("0");
else printf("%d\n",color[pos]);
}
int main()
{
int a,b;
while(cin>>n>>m)
{
memset(head1,-1,sizeof(head1));
memset(head2,-1,sizeof(head2));
tot1=tot2=0;
for(int i=1;i<=m;i++)
{
cin>>a>>b;
add(a,b);//反边一起加的
}
Kosaraju();
solve();
}
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: