//复习一遍,加了点注释
#include<bits/stdc++.h>
using namespace std;
const int N=1000010;
int ver[2*N],nxt[2*N],head[N],tot;
int n,m,p,num,low[N],dfn[N],fa[N];
int d[N],s[2*N],q[2*N],ans; //s: vertex in cycle
void add(int x,int y) {
ver[++tot]=y,nxt[tot]=head[x],head[x]=tot;
}
void solve_cycle(int x,int y) {
for( p=0; y!=x ; y=fa[y]) s[++p]=y; //s为环上的点
s[++p]=x; //x是环的入点
//环断开,复制一遍,DP求基环树直径的方法
for(int i=1; i<=p; i++) s[p+i]=s[i];
int l=1, r=0;
for(int i=1; i<=2*p; i++) {
while(l<=r && q[l]<i-p/2)l++; //去除单调队列中的不合格数据,不绕远
if(l<=r)ans=max(ans,d[s[i]]+d[s[q[l]]]+i-q[l]); //环上两分支距离最远吗
while(l<=r && d[s[q[r]]]-q[r]<=d[s[i]]-i)r--; //去除单调队列中的无效数据
q[++r]=i; //加入单调队列
}
//基环树最高点往下走,最深可以走多远
for(int i=1; i<=p; i++)
d[x]=max(d[x],d[s[i]]+min(p-i,i));
}
void tarjan(int x) {
dfn[x]=low[x]=++num;
for(int i=head[x]; i; i=nxt[i]) {
int y=ver[i];
if(!dfn[y]) {
fa[y]=x; //x->y,如有重边,fa[y]=i,才能正确处理
tarjan(y);
low[x]=min(low[x],low[y]);
if(low[y]>dfn[x]) { //不在环中
ans=max(ans,d[x]+d[y]+1); //DP求树直径的方法
d[x]=max(d[x],d[y]+1);
}
} else if(y!=fa[x]) //不能回指
low[x]=min(low[x],dfn[y]);
}
//最后处理与x相连的环,前面可能已经处理了多个环
for(int i=head[x]; i; i=nxt[i]) {
int y=ver[i];
if(x!=fa[y] && dfn[y]>dfn[x]) //x->y,但y不是从x搜索过来,所以x--->y->x
solve_cycle(x,y); //处理环,x是环的入点
}
}
int main() {
cin>>n>>m;
tot=1; //成对存储,重2开始位号,Add时++tot
for(int i=1; i<=m; i++) {
int k,x,y;
scanf("%d%d",&k,&x);
for(;--k;x=y) {
scanf("%d",&y);
add(x,y),add(y,x);
}
}
tarjan(1);
cout<<ans<<endl;
return 0;
}