注意:
tarjan缩点,对于有向、无向图依然成立。因为是min操作,所以是不断更新的。
tarjan缩点,对于dp树的操作,从孩子结点贡献往上增加,这样是不对的:
如图,不是环所以不会被删除,但是如果dp树通过拓扑排序实现的话,3的结点被2记录之后,又通过1再次被记录。
缩点后的本来就不一定是树的(一般都是有向图,如果是无向图肯定还是树)
T1:洛谷P2341
求被所有点都能到达的点有多少个。
题解:首先考虑到有环、对于环、环内所有点都是互相指向的,所以可以缩点看成一个点。对于这个点的贡献是强连通分量里的个数。答案要求的点显然是没有出边的,即不指向别的点。所以记录建新图后的出度判断0即可。
但是由于新图可能有多个完全分开的子图,那样会有多个出度为0的点,或者本身的图就有多个出度为0的点。那样一定没有答案所需要的点。特判即可。
#include<bits/stdc++.h>
#define FOR(i,l,r) for(int i=l;i<=r;i++)
using namespace std;
const int maxm = 100005;
const int maxv = 20005;
struct Edge{
int from,to,dist;
Edge(){}
Edge(int _from,int _to,int _dist):from(_from),to(_to),dist(_dist){}
};
int n,m;
int etop=1,dfscnt,scccnt;
int he[maxv],ne[maxm];
int sccnum[maxv],low[maxv],dfn[maxv];
int L[maxm],R[maxm];
int dp[maxv],ind[maxv];
stack<int>st;
Edge ed[maxm];
void insert(int u,int v,int w){
ed[etop]=Edge(u,v,w);
ne[etop]=he[u];
he[u]=etop++;
}
void tarjan(int now){
st.push(now);
dfn[now]=low[now]=++dfscnt;
for(int i=he[now];i;i=ne[i]){
Edge &e=ed[i];
if(!dfn[e.to]){
tarjan(e.to);
low[now]=min(low[now],low[e.to]);
}else if(!sccnum[e.to]){
low[now]=min(low[now],dfn[e.to]);
}
}
if(low[now]==dfn[now]){
scccnt++;
int x;
do{
x=st.top();st.pop();
sccnum[x]=scccnt;
dp[scccnt]++;
}while(x!=now);
}
}
int main(){
cin>>n>>m;
FOR(i,1,m){
int x,y;
scanf("%d%d",&x,&y);
insert(x,y,0);
L[i]=x,R[i]=y;
}
FOR(i,1,n)if(!sccnum[i])tarjan(i);
etop=1;
memset(he,0,sizeof(he));
memset(ne,0,sizeof(ne));
FOR(i,1,m){
int x=sccnum[L[i]],y=sccnum[R[i]];
if(x!=y){
insert(x,y,0);
ind[x]++;
}
}
int ans=0;
FOR(i,1,scccnt)if(!ind[i]){
if(ans){
ans=0;
break;
}
else ans=dp[i];
}
cout<<ans<<endl;
}