题目链接
http://poj.org/problem?id=2186
题目大意
N个点 (1 <= N <= 10,000), M条边 (1 <= M <= 50,000),(A B)表示点A指向点B,输出有多少个点是任意点可以指向的
样例
输入
3 3
1 2
2 1
2 3
输出
1
思路
强连通分量
邻接表
答案需考虑特例:缩点后依旧存在多个强连通分量
这题数据范围m给小了应该是m<=500000
法一
1.时间戳
2.进栈
3.深搜
4.回溯
4.1.low修改
4.2时间戳=父节点时间戳时弹栈到到当前节点
法二
1.正向建边深搜,退栈时打时间戳,使得时间戳最大的点是没有入度的点
2.反向建边,从时间戳大的点开始深搜,搜索到的点是一个强连通分量
法一弱智代码
#include<cstdio>
#include<cstring>
const int nn=10005;
const int mm=500005;
struct rec{int a,b,h;}original[mm];
int n,m,h[nn],a,b,low[nn],dfn[nn],flag[nn],time,sl,st[nn];
int num,sum[nn],now[nn],ans,send[nn],f;
int min(int x,int y){return x<y ?x:y;}
int dfs(int k){
dfn[k]=low[k]=++time;
st[++sl]=k; flag[k]=1;
for(int x_h=h[k]; x_h!=-1; x_h=original[x_h].h){
int y=original[x_h].b;
if(flag[y]!=0)low[k]=min(low[k],low[y]);
if(dfn[y]==0)low[k]=min(low[k],dfs(y));
}
if(dfn[k]==low[k]){
++num;
int summ=0;
do{
++summ;
flag[st[sl]]=0;
now[st[sl--]]=num;
}while(st[sl+1]!=k);
sum[num]=summ;
}
return(low[k]);
}
int main(){
//freopen("poj2186.out","w",stdout);
scanf("%d%d",&n,&m);
memset(h,-1,sizeof(h));
for(int i=1; i<=m; ++i){
scanf("%d%d",&a,&b);
original[i].a=a;
original[i].b=b;
original[i].h=h[a];
h[a]=i;
}
for(int i=1; i<=n; ++i)low[i]=nn;
time=0; sl=0;
memset(dfn,0,sizeof(dfn));
memset(flag,0,sizeof(flag));
for(int i=1; i<=n; ++i)if(dfn[i]==0)dfs(i);
memset(send,0,sizeof(send));
for(int i=1; i<=m; ++i)
if(now[original[i].a]!=now[original[i].b])
++send[now[original[i].a]];
ans=0;
f=0;
for(int i=1; i<=num; ++i){
if(send[i]==0){
if(f==1){
ans=0;
break;
}else{
ans=sum[i];
f=1;
}
}
}
printf("%d\n",ans);
return 0;
}