对于一些题目,我们找出强连通分量后,就会变得非常简单=v=+
首先介绍强连通:
对于一个有向图,每一对点(x,y)都可以相互到达,则称之为强连通图。
而一个有向图中的极大强连通子图,就称为强连通分量
(注:极大的意思就是说不能再往这个子图中添加点,即当前情况下的最大子图
(强连通都是:环,环套环,环套环套环……
接下来就是求出强连通分量:这里只介绍tarjan算法
定义时间戳dfn[i]表示i是第几个被访问到的节点。
定义返祖数组low[i]表示本块强连通分量中最早被访问的节点的时间
进行深搜,把路径上所有的点压入一个栈中。
那么什么时候弹出?
当一个强连通分量中所有点都被遍历的时候。一个最显而易见的条件:回溯到强连通分量最早被访问的节点。
根据low和dfn的定义,我们可以知道当low[x]==dfn[x]时,我们得到了一个包含x的强连通分量,此时就可以把x到栈顶的所有元素弹出并记录。
那么low[x]如何更新?
① 子节点回溯得到
② 访问到一个已被访问且未被弹出的点
tarjan-dfs部分
inline void dfs(int x)
{
dfn[x]=low[x]=++index;
sta[++top]=x;
ins[x]=1;
for(int i=f[x];i;i=next[i])
{
int t=poi[i];
if(!dfn[t]) { dfs(t);low[x]=min(low[x],low[t]);}
else
if(ins[t])
low[x]=min(low[x],low[t]);
}
if(dfn[x]==low[x])
{
while(1)
{
cnt++;
int t=sta[top];
top--;
ins[t]=0;
lin[t]=cnt;
XXXXXXXXX(依题目决定)
if(t==x) break;
}
}
}
所谓tarjan缩点,就是把整个强连通分量当作一个点来进行处理。
显然得到一个性质,缩点后的图不存在环。
下面以 bzoj1051/luogu2341 HAOI2006 受欢迎的牛为例
传送门
这道题的大意就是说有多少头牛被其他所有的牛喜欢,喜欢具有传递性。
乍一看。。。floyd传递闭包?
再一看数据- -
行吧,算我输
经过观察可以发现,我们如果把他们的关系绘成图有以下性质
① 一个强连通子图中所有奶牛都相互喜欢
② 若强连通子图A中有奶牛喜欢强连通子图B中的奶牛,则所有A中奶牛都喜欢所有B中奶牛
那么仔细思考一下,如果我们通过tarjan缩点,把整个图变成有向无环图,是不是就成了一道水题?
因为无环,所有要被所有奶牛崇拜,则这个强连通分量不会有边连到其他的强连通分量,否则就会构成环,即出度为0。
朴素的想法,如果一个强连通分量的入度=分量总数-1,并且出度=0,则满足条件。
但是如果动下脑子,,就可以想到一个巧妙的办法:
当出度为0的点>1个,则 入度=分量总数-1 条件绝对不成立
答案为0
如果出度为0的点=0个 则 出度=0 条件绝对不成立
答案为0
那么显然,如果答案要不为0,出度为0的点=1个!
答案即为这个点的权值(即缩成这个点的强连通分量所包含节点的数量)
CODE:
#include
#include
#include
#include
using namespace std;
bool ins[10001];
int index=0,top=0,cnt=0,n,m;
int sta[10001],dfn[10001],low[10001],poi[100001],next[100001],f[10001],lin[10001],x[50001],y[50001],num[10001],vis[10001],cant[10001];
inline void add(int x,int y)
{
poi[++cnt]=y;next[cnt]=f[x];f[x]=cnt;
}
inline void dfs(int x)
{
dfn[x]=low[x]=++index;
sta[++top]=x;
ins[x]=1;
for(int i=f[x];i;i=next[i])
{
int t=poi[i];
if(!dfn[t]) { dfs(t);low[x]=min(low[x],low[t]);}
else
if(ins[x])
low[x]=min(low[x],low[t]);
}
if(dfn[x]==low[x])
{
while(1)
{
int t=sta[top];
top--;
ins[t]=0;
lin[t]=x;
num[x]++;
vis[x]=1;
if(t==x) break;
}
}
}
inline void out()
{
for(int i=1;i<=m;i++)
if(lin[x[i]]!=lin[y[i]]) cant[lin[x[i]]]=1;
int hh=0,tx=-1;
for(int i=1;i<=n;i++)
if(!cant[i]&&vis[i])
{
tx=i;
hh++;
if(hh==2)
{
printf("0\n");
return;
}
}
if(tx==-1) {printf("0\n");return;}
printf("%d\n",num[tx]);
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
{
scanf("%d%d",&x[i],&y[i]);
add(x[i],y[i]);
}
for(int i=1;i<=n;i++)
{
if(!dfn[i]) dfs(i);
}
out();
}