#include<iostream>
using namespace std;
struct node{int w,next,to;};//w:权值,next:同一起点的下一条边的序号,to终点
node bian[10010];//边
int head[10001];//起点的索引表
int n,m,num,cnt,bcnt;
//n个点m条边num是时间序号,cnt是强连通分量(出栈次数),bcnt记录边的条数
int vist[10010];//标记有没有访问过
int ins[10010];//标记顶点在不在栈中
int sp=1;//栈顶指针
int zhan[10010];//栈
int dfn[10010];//每个顶点是第几个被访问的
int low[10010];//标记是不是一伙人,如果出边被遍历完且dfn[]==low[]就出栈
//low[x]=min(low[x],dfn[y]);x遍历到y
void push(int x){//入栈
zhan[sp++]=x;
}
int pop(){//出栈
if(sp==1)//栈空
return 987654321;
else sp--;
}
void add(int x,int y,int w){//起点终点权值
bcnt++;//bcnt:边的总条数
bian[bcnt].w=w;//终点赋值
bian[bcnt].to=y;//权值赋值
bian[bcnt].next=head[x];//当前同一起点下一条边的序号设置
head[x]=bcnt;//头插法:x的第一条出边修改为当前边序号设置
}
void tj(int x){//开始遍历tarjan,求强连通分量个数,缩点
int y,i,t;
num++;//访问的顶点次序(时间轴)+1;
dfn[x]=low[x]=num;//默认的low=dfn,后面low有可能被刷新变小
push(x);//tj入栈
ins[x]=1;//在栈中做标记
vist[x]=1;//标记x已经被标记过了
for(int i=head[x];i;i=bian[i].next){//扫描x的所有出边
y=bian[i].to;
if(vist[y]==0){//第1种情况:y没有被访问
tj(y);//继续递归以y为起点
low[x]=min(low[x],dfn[y]);//递归回填改变low,同一个强连通分量的low基本相同(不一定)
}
else {//y已经被访问过,判断y是否在栈中,如果不在栈中y就已经是其他的连通分量的一个点,跳过
if(ins[y]==1){//y已经被访问过了并且y在栈中,说明x->y是返祖边
//不要递归了,直接修改low
low[x]=min(low[x],dfn[y]);
}
}
}//for循环结束后,x的所有出边穷举完毕,判断low[x]==dfn[x],如果相等就一直出栈直到x点出栈,这一次出栈的所有点是同一个强连通分量
if(low[x]==dfn[x]){//如果x找到了所有能作为同一个强连通分量的顶点后,同一个强连通分量的所有点开始出栈直到x出栈
cnt++;//强连通分量的个数+1;
cout<<"第"<<cnt<<"个强连通分量的元素为:";
do{//方便判断用do-while
t=pop();//出栈
if(t==987654321)break;//栈空则停止
cout<<" "<<t;//输出元素
ins[t]=0;//出栈标记
}
while(t!=x);//x出栈后就停止
}
}
int main(){
cin>>n>>m;
for(int i=1;i<=m;i++){
int x,y;
cin>>x>>y;
add(x,y,1);
}
for(int i=1;i<=n;i++){//防止有的强连通分量是单独的,不与别的强连通分量有边相连导致找不到
if(vist[i]==0)tj(i);
}
cout<<"总的强联通分量的个数为:"<<cnt<<endl;
for(int i=1;i<=n;i++){
cout<<"第"<<i<<"个点的low值为:"<<low[i]<<endl;
}
return 0;
}