题面传送门
这道题很明显是要我们求强连通分量。我们可以用
T
a
r
j
a
n
Tarjan
Tarjan求。
对于
T
a
r
j
a
n
Tarjan
Tarjan的求法,是这样做的:
记录第几个访问到:用一个
d
f
n
dfn
dfn数组。
记录这个点的子树能访问到的最早访问到的点,为
l
o
w
low
low。
记录一个栈,表示没有弹出的点。
记录
v
i
s
vis
vis,表示是否在栈里。
对于每个点,从它的边走出去的一个点
v
v
v。
若这个点在栈中,那么这个店肯定不属于这个强连通分量,那么就不用搜索,直接获取即可。
如果不在栈中,那么就搜索,并更新答案。
最后如果
l
o
w
=
=
d
f
n
low==dfn
low==dfn,那么表示没有走出去,这个店及以下没有弹出的点都属于一个强连通分量内。
但那些在这个店以下但已经弹出的点呢?
因为已经弹出,所以一定属于另一个强连通分量内。而这两个强连通分量肯定不能合并成一个,否则可以指到更上面。
总时间复杂度
O
(
n
+
m
)
O(n+m)
O(n+m)
代码实现:
#include<cstdio>
#include<cstring>
#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)<(b)?(a):(b))
using namespace std;
int n,m,x,y,st[10039],dfn[10039],vis[10039],low[10039],head,h[10039],ans[10039],pus,tot,now,dfs,he,fs[10039];
struct yyy{
int to,z;
}f[100039];
inline void add(int x,int y){
f[++head]=(yyy){y,h[x]};
h[x]=head;
}
inline void tarjan(int x){
low[x]=dfn[x]=++dfs;
st[++he]=x; vis[x]=1;
int cur=h[x];
yyy tmp;
while(cur!=-1){
tmp=f[cur];
if(!dfn[tmp.to])tarjan(tmp.to),low[x]=min(low[x],low[tmp.to]);
else if(vis[tmp.to]) low[x]=min(low[x],low[tmp.to]);
cur=tmp.z;
}
if(low[x]==dfn[x]){
tot++;
while(st[he+1]!=x){
ans[st[he]]=tot;
vis[st[he]]=0;
he--;
}
}
}
int main(){
memset(h,-1,sizeof(h));
register int i;
scanf("%d%d",&n,&m);
for(i=1;i<=m;i++) scanf("%d%d",&x,&y),add(x,y);
for(i=1;i<=n;i++) if(!dfn[i]) tarjan(i);
for(i=1;i<=n;i++) fs[ans[i]]++;
for(i=tot;i>=1;i--) if(fs[i]>=2) pus++;
printf("%d\n",pus);
}