受欢迎的牛
受欢迎的牛 jdoj-neooj-1076
题目大意:给你n个点,m条单项边,求可以和所有点联通的点的个数。
注释:n<=10,000,m<=50,000
想法:这题也是一道tarjan裸题,让我来A掉吧!这题和爱在心中(爱在心中?猛戳)类似,只不过这题有坑... ...用panxf的模板A不掉这道题... ...为什么呢?因为n的数据范围,会MLE。但是我们发现,这道题只需要求出强连通分量之中点的个数。所以,我们可以将原本记录强连通分量的点的数组f压成一维。但是,我们不可以用邻接矩阵来存边了。同样的,我们用链式前向星,保证不会MLE。而且,我们在这里需要作出一个特判(不然会WA掉),就是如果存在一个点,这个点入度和出度都为0,最后的答案一定为0,这是显然的。下面,我们想怎样才能正确的求出个数。同样的,我们枚举出度为0的强连通分量,如果个数大于1,显然个数为0。否则,就是这个强联通分量的个数。
最后,附上丑陋的代码... ...
1 #include <iostream> 2 #include <cstdio> 3 #define N 10010 4 #define M 50010 5 using namespace std; 6 int x[M],y[M]; 7 int to[M],nxt[M],head[N],mid; 8 int z[N],deep[N],low[N],f[N]; 9 int inwhat[N],chu[N]; 10 int tot,top,ans; 11 bool v[N],isv1[N],isv2[N]; 12 int inz[N]; 13 void add(int a,int b)//存边 14 { 15 to[++mid]=b; 16 nxt[mid]=head[a]; 17 head[a]=mid; 18 } 19 int n; 20 void tarjan(int x)//tarjan 21 { 22 z[++top]=x; 23 v[x]=inz[x]=1; 24 low[x]=deep[x]=++tot; 25 for(int i=head[x];i;i=nxt[i])//枚举链式前向星 26 { 27 if(!v[to[i]]) tarjan(to[i]),low[x]=min(low[x],low[to[i]]); 28 else if(inz[to[i]]) low[x]=min(low[x],deep[to[i]]); 29 } 30 if(deep[x]==low[x]) 31 { 32 int t; 33 ans++; 34 do 35 { 36 t=z[top--];inz[t]=0; 37 f[ans]++;//这里不用记录强连通分量里具体的点,只需要个数即可。 38 inwhat[t]=ans; 39 }while(t!=x); 40 } 41 } 42 int main() 43 { 44 int m; 45 scanf("%d%d",&n,&m); 46 int a,b; 47 for(int i=1;i<=m;i++) 48 { 49 scanf("%d%d",&x[i],&y[i]); 50 isv1[x[i]]=1;//出度 51 isv2[y[i]]=1;//入度 52 add(x[i],y[i]); 53 } 54 for(int i=1;i<=n;i++) 55 { 56 if(isv1[i]&&!v[i]) tarjan(i);//枚举整张图 57 } 58 if(n>=2)//特判 59 { 60 for(int i=1;i<=n;i++) if(isv1[i]==0&&isv2[i]==0) 61 { 62 printf("0\n"); 63 return 0; 64 } 65 } 66 for(int i=1;i<=m;i++) 67 { 68 if(inwhat[x[i]]!=inwhat[y[i]]) chu[inwhat[x[i]]]++; 69 } 70 int cnt=0; 71 int middle=0; 72 for(int i=1;i<=ans;++i) if(chu[i]==0) cnt++,middle=i; 73 if(cnt>1) 74 { 75 printf("0\n"); 76 return 0; 77 } 78 else printf("%d\n",f[middle]);//输出即可。 79 return 0; 80 }
小结:tarjan是一个O(n)的算法,极其经典,正在getting中。
错误:在记录出度的时候,不是chu[x[i]]++,而是chu[inwhat[x[i]]]++。
| 欢迎来原网站坐坐! >原文链接<