tarjan缩点
缩点是图论中常用的技巧,当路径上贡献具有传导性时,可以将一个强连通分量缩成一个新点,因为一个强连通分量内的点可以互相到达。强连通分量内的点的个数可以通过染色记录,具有同一种颜色的点的个数即为该强连通分量内点的个数。
例题:poj2186 Popular Cows
告诉你有n头牛,m个崇拜关系,并且崇拜具有传递性,如果a崇拜b,b崇拜c,则a崇拜c,求最后有几头牛被所有牛崇拜。
Sample Input
3 3
1 2
2 1
2 3
Sample Output
1
题解:
先考虑整张图无环时(DAG)的情况,当存在一头牛被所有牛崇拜时,只需考虑出度为0的点的个数(出度为0的点的个数一定大于等于1),若为1,则有解,若大于1,则无解;
若图中有环,则用tarjan将图中强连通分量找出后,对于点u相连的点v,若他们的颜色相同,说明他们处于同一个强连通分量,视作u,v在一个超级点中,不统计在u的出度内;若他们的颜色不同,则说明这两个点属于不同的强连通分量中,即可让u的度数加1.
统计完成后,对于每种颜色(即所有强连通分量)统计度数为0的点,即可得到被所有牛崇拜的“超级点”个数。注意,若该点为点数大于1的强连通分量构成的“超级点”,则要将该强连通分量内的点数加入答案中,而不是加1.
#include<cstdio>
#include<vector>
#include<cstring>
using namespace std;
int n,m;
const int maxn=10010;
int dfn[maxn],low[maxn],cnt[maxn],color[maxn],degree[maxn],vis[maxn];
int tot;
int st[maxn],top;
int tmp,ans;
int deep;
vector<int> g[maxn];
void tarjan(int u)
{
dfn[u]=++deep;
low[u]=deep;
vis[u]=1;
st[++top]=u;
int sz=g[u].size();
for(int i=0;i<sz;i++)
{
int v=g[u][i];
if(!dfn[v])
{
tarjan(v);
low[u]=min(low[u],low[v]);
}
else
{
if(vis[v])
low[u]=min(low[u],low[v]);
}
}
if(dfn[u]==low[u])
{
color[u]=++tot;
vis[u]=0;
while(st[top]!=u)
{
color[st[top]]=tot;
vis[st[top--]]=0;
}
top--;
}
}
int main()
{
while(~scanf("%d%d",&n,&m))
{
memset(vis,0,sizeof(vis));
memset(dfn,0,sizeof(dfn));
memset(color,0,sizeof(color));
memset(degree,0,sizeof(degree));
memset(low,0,sizeof(low));
memset(cnt,0,sizeof(cnt));
memset(st,0,sizeof(st));
for(int i=1;i<=m;i++)
{
int x,y;
scanf("%d%d",&x,&y);
g[x].push_back(y);
}
for(int i=1;i<=n;i++)
if(!dfn[i])tarjan(i);
for(int i=1;i<=n;i++)
{
int sz=g[i].size();
for(int j=0;j<sz;j++)
{
int v=g[i][j];
if(color[i]!=color[v])
degree[color[i]]++;
}
cnt[color[i]]++;
}
for(int i=1;i<=tot;i++)
if(degree[i]==0) tmp++,ans=cnt[i];
if(tmp>1) printf("0\n");
else printf("%d\n",ans);
}
return 0;
}