强连通分量tarjan缩点——POJ2186 Popular Cows

这里的Tarjan是基于DFS,用于求有向图的强联通分量。

运用了一个点dfn时间戳和low的关系巧妙地判断出一个强联通分量,从而实现一次DFS即可求出所有的强联通分量。

§有向图中, u可达v不一定意味着v可达u.
   相互可达则属于同一个强连通分量
   (Strongly Connected Component, SCC)
§有向图和它的转置的强连通分量相同
§所有SCC构成一个DAG(有向无环图)

dfn[u]为节点u搜索的次序编号(时间戳),即首次访问u的时间

low[u]为u或u的子树能够追溯到的最早的栈中节点的次序号,即它所在的某个强联通分量(可能是自己一个点,也可能是一棵树)对应的搜索子树的根的访问时间

不难知道,当dfn(u)=low(u)时,以u为根的搜索子树上所有节点是一个强连通分量。

伪代码如下

tarjan(u)
{
DFN[u]=Low[u]=++time // 为节点u设定次序编号和Low初值
Stack.push(u) // 将节点u压入栈中
for each (u, v) in E // 枚举以u为起点的每一条边
if (v is not visted) // 如果节点v未被访问过
tarjan(v) // 继续向下找
Low[u] = min(Low[u], Low[v])
else if (v in S) // 如果节点v还在栈内,则形成一个强联通分量,low值为根的dfn
Low[u] = min(Low[u], DFN[v])//注意到环可能不止一个,有可能是环套环,所以我们的环是要最大的,也就是访问该子树根的时间要尽可能的早
if (DFN[u] == Low[u]) // 如果节点u是强连通分量的根
repeat
v = S.pop // 将v退栈,为该强连通分量中一个顶点
print v
until (u==v)
}

至于缩点,我们可以强行重新建一个图,但有时我们只需要新图的一些性质,所以有时我们可以不需要重新建图,只统计我们需要的数据就可以了。一个例子可以看下面的例题。

POJ2186 Popular Cows

题目描述

每头奶牛都梦想成为牛棚里的明星。被所有奶牛喜欢的奶牛就是一头明星奶牛。所有奶

牛都是自恋狂,每头奶牛总是喜欢自己的。奶牛之间的“喜欢”是可以传递的——如果A喜

欢B,B喜欢C,那么A也喜欢C。牛栏里共有N 头奶牛,给定一些奶牛之间的爱慕关系,请你

算出有多少头奶牛可以当明星。

输入输出格式

输入格式:
第一行:两个用空格分开的整数:N和M

第二行到第M + 1行:每行两个用空格分开的整数:A和B,表示A喜欢B

输出格式:
第一行:单独一个整数,表示明星奶牛的数量

输入输出样例

输入样例#1:
3 3
1 2
2 1
2 3
输出样例#1:
1
说明

只有 3 号奶牛可以做明星

【数据范围】

10%的数据N<=20, M<=50

30%的数据N<=1000,M<=20000

70%的数据N<=5000,M<=50000

100%的数据N<=10000,M<=50000

 牛的相互喜欢构成了一个有向图,题目则要求我们求出一个点,这个点是其他所有点都可以到达的。

对于形成环的点,他们之间能相互到达,所以我们可以把它们缩成一个点。

这样我们通过tarjan缩点之后就形成了一个DAG,分析可以知道一个其他所有点都能到达的点的出度一定为0(因为缩点后不存在环,所以一旦某个点有出度,则出度所指向的点无法再返回到原来的点了),所以如果有一个出度为0的点,这个点所对应的牛的数量就是答案明星奶牛的数量,但是如果有两个以上的出度为0的点,某两个出度为0的点必定无法相互到达,故不存在题目所说的明星奶牛,为0.

 #include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
const int tvt=;
const int tot=;
using namespace std;
int dfn[tvt],low[tvt],zhan[tvt],out[tvt],head[tot],next[tot],to[tot],belong[tvt],total[tvt],inde,n,m,len,ans,num; //out负责统计点的出度,total负责统计某个强联通分量的点的个数,inde就是时间(戳),ans统计出度为的个数
void add(int u,int v){
num++;
next[num]=head[u];
to[num]=v;
head[u]=num;
}
void tarjan(int u){
dfn[u]=low[u]=++inde;
zhan[++len]=u;
for (int i=head[u];i!=;i=next[i]){
int v=to[i];
if (!dfn[v]){
tarjan(v);
low[u]=min(low[v],low[u]);
}
else low[u]=min(low[u],dfn[v]);
}
if (dfn[u]==low[u])
do{int v=zhan[len--];
belong[v]=u;
out[v]=;
total[u]++;
}while(zhan[len+]!=u); //即(v==u)
}
int main(){
scanf("%d%d",&n,&m);
num=;
memset(next,,sizeof(next));
memset(zhan,,sizeof(zhan));
memset(belong,,sizeof(belong));
memset(total,,sizeof(total));
memset(head,,sizeof(head));
memset(dfn,,sizeof(dfn));
memset(low,,sizeof(low));
memset(out,,sizeof(out));
memset(to,,sizeof(to));
for (int i=;i<=m;i++){
int x=,y=;
scanf("%d%d",&x,&y);
add(x,y);//添边
}
len=;
for (int i=;i<=n;i++)
belong[i]=i;
inde=;
for (int i=;i<=n;i++)
if (!dfn[i]) tarjan(i);
for (int i=;i<=n;i++)
for (int j=head[i];j!=;j=next[j]){
int v=to[j];
if (belong[i]!=belong[v]) out[belong[i]]++;//缩点,不在同一个强联通分量的话就视为两个点;把在同一个强联通分量的点的出度都统计到一个点上,达到“缩点效果”
}
ans=;
for (int i=;i<=n;i++){
if ((belong[i]==i)&&(!out[i])) ans++;
if (ans>=) break;
}
if (ans==) for (int i=;i<=n;i++) if ((belong[i]==i)&&(!out[i])){
cout<<total[i]<<endl;
return ;
}
else;
else cout<<''<<endl;
return ;
}

神奇的代码

tarjan后,我们只需要点的出度,那么我们进行“缩点”,考察缩点后点的性质,对于在同一个强联通分量的点它们之间连线产生的出度就不计,就只计不在同一个强联通分量的点的出度,并且对同一个强联通分量的所有点的出度都集中到这个分量的某个点上,这样我们就达到了我们需要的效果的“缩点”。belong数组能够告诉我们两个点是不是在同一个强联通分量里

所以,缩点实际上是根据缩点后的我们需要的性质是怎么变化的,我们就针对那个性质去统计。

上一篇:POJ2186 Popular Cows 强连通分量tarjan


下一篇:\(\S1\) 描述性统计