/*--------------------------------------------------------------*/
/* ACM ICPC 2007-2008                                           */
/* Northeastern European Regional Contest                       */
/* St Petersburg - Barnaul - Tashkent - Batumi, November 28, 200*/
/*--------------------------------------------------------------*/
/* Problem C. Cactus Reloaded                                   */
/*--------------------------------------------------------------*/
/* Solution                                                     */
/*                                                              */
/* Author                Roman Elizarov                         */
/*--------------------------------------------------------------*/
 
/*
    Solution for NEERC'2007 Problem C: Cactus Reloaded
    (C) Roman Elizarov
    Note: this solution attempts to check correctness of the input
*/

import java.io.*;
import java.util.*;

public class cactus_re {

	public static void main(String[] args) throws Exception {
		new cactus_re().go();
	}

	public static class Cycle {
		final List<Integer> v = new ArrayList<Integer>();

		public String toString() {
			return v.toString();
		}
	}

	public static class Heap {
		int[] index;
		int[] depth;
		int[] pos;
		int size;

		public Heap() {
			realloc(10);
		}

		private void realloc(int n) {
			index = new int[n];
			depth = new int[n];
			pos = new int[n];
		}

		public void init(int n) {
			if (index.length < n)
				realloc(n);
			size = 0;
		}

		public void add(int index0, int depth0) {
			int p = size++;
			index[p] = index0;
			depth[p] = depth0;
			pos[index0] = p;
			while (p > 0 && depth[p] > depth[(p - 1) / 2]) {
				int next = (p - 1) / 2;
				swap(p, next);
				p = next;
			}
		}

		public void removeByIndex(int index0) {
			int p = pos[index0];
			swap(p, --size);
			while (2 * p + 1 < size) {
				int next = 2 * p + 1;
				if (2 * p + 2 < size && depth[2 * p + 2] > depth[next])
					next = 2 * p + 2;
				if (depth[p] >= depth[next])
					break;
				swap(p, next);
				p = next;
			}
		}

		public int getMaxDepth() {
			assert size > 0;
			return depth[0];
		}

		private static void swap(int[] a, int i, int j) {
			int t = a[i];
			a[i] = a[j];
			a[j] = t;
		}

		private void swap(int i, int j) {
			swap(index, i, j);
			swap(depth, i, j);
			pos[index[i]] = i;
			pos[index[j]] = j;
		}
	}

	List<List<Integer>> adj = new ArrayList<List<Integer>>();
	List<Set<Integer>> adjs = new ArrayList<Set<Integer>>(); // for validation only

	List<Map<Integer, Cycle>> next = new ArrayList<Map<Integer, Cycle>>();
	Stack<Integer> st = new Stack<Integer>();
	PriorityQueue<Integer> tops = new PriorityQueue<Integer>(10, Collections.reverseOrder());
	Heap heap = new Heap();

	int[] up;
	int[] idx;
	int[] vnum;
	int[] depth;
	int diam;
	int curnum;

	void go() throws Exception {
		// read input
		XScanner in = new XScanner(new File("cactus.in"));
		int n = in.nextInt();
		int m = in.nextInt();
		in.nextLine();

		assert n >= 1 && n <= 50000;
		assert m >= 0 && m <= 10000;

		for (int i = 0; i <= n; i++) {
			adj.add(new ArrayList<Integer>());
			adjs.add(new HashSet<Integer>());
			next.add(new HashMap<Integer, Cycle>());
		}
		
		for (int i = 0; i < m; i++) {
			int k = in.nextInt();
			assert k >= 2 && k <= 1000;
			int u = 0;
			for (int j = 0; j < k; j++) {
				int v = in.nextInt();
				assert v >= 1 && v <= n;
				if (u > 0) {
					assert u != v;
					add(u, v);
					add(v, u);
				}
				u = v;
			}
			in.nextLine();
		}
		in.close();

		// solve
		up = new int[n + 1];
		vnum = new int[n + 1];
		idx = new int[n + 1];
		depth = new int[n + 1];

		// follow all edges in depth-first order (explicit stack)
		int s0 = new Random(n * m).nextInt(n); // prng start
		pushv(s0);
		while (!st.isEmpty()) {
			int s = st.peek();
			int i = idx[s]++;
			if (i >= adj.get(s).size()) {
				// done with it -- backtrack and compute depth
				compute(s);
				st.pop();
				continue;
			}
			int t = adj.get(s).get(i);
			visit(s, t);
		}

		assert curnum == n : "Connected";
		
		// write output
		PrintWriter out = new PrintWriter(System.out);
		out.println(diam);
		out.close();
	}

	void add(int a, int b) {
		assert !adjs.get(a).contains(b);
		adj.get(a).add(b);
		adjs.get(a).add(b);
	}

	void visit(int s, int t) {
		if (vnum[t] > 0) {
			if (vnum[s] < vnum[t])
					return; // already processed
			if (up[s] == t)
					return; // one back -- skip (cycle code will not change anything)
			// cycle
			Cycle c = new Cycle();
			int j = s;
			while (j != t) {
				c.v.add(j);
				j = up[j];
			}
			c.v.add(t);
			Collections.reverse(c.v);
			// now from t to s
			for (int i = 1; i < c.v.size(); i++) {
				int p = c.v.get(i - 1);
				int q = c.v.get(i);
				Cycle oldc = next.get(p).remove(q);
				assert oldc.v.size() == 2 : "Cactus -- at most one loop per edge";
			}
			next.get(t).put(c.v.get(1), c);
			return;
		}
		pushv(t);
		up[t] = s;
		Cycle c = new Cycle();
		c.v.add(s);
		c.v.add(t);
		next.get(s).put(t, c);
	}

	private void pushv(int t) {
		vnum[t] = ++curnum;
		st.push(t);
	}

	private void compute(int s) {
		// Option 1 -- Max diameter via one cycle
		for (Cycle c : next.get(s).values()) {
			assert depth[s] == 0 : "Not intialized yet";
			int len = c.v.size();
			heap.init(len);
			// init with dists from 0 to first closest half
			for (int i = 1; i <= len / 2; i++)
				heap.add(i, depth[c.v.get(i)] + i);
			int delta = 0;
			for (int j = 0; j < len; j++) {
				// compute from j to first closest half
				diam = Math.max(diam, depth[c.v.get(j)] + heap.getMaxDepth() + delta);
				// adjust range with dists from j+1 to first closest half
				delta--;
				heap.removeByIndex((j + 1) % len);
				int k = (j + 1 + len / 2) % len;
				heap.add(k, depth[c.v.get(k)] + len / 2 - delta);
			}
		}

		// Option 2 -- Max diameter via two neighbouring cycles (also finds depth[s])
		tops.clear();
		for (Cycle c : next.get(s).values()) {
			int maxd = 0;
			int len = c.v.size();
			for (int i = 1; i < len; i++) {
				int t = c.v.get(i);
				int d = depth[t] + Math.min(i, len - i);
				maxd = Math.max(d, maxd);
			}
			tops.add(maxd);
		}

		// Update depth
		if (!tops.isEmpty())
			depth[s] = tops.peek();

		// Update diameter
		int d2 = 0;
		if (!tops.isEmpty())
			d2 += tops.remove();
		if (!tops.isEmpty())
			d2 += tops.remove();

		diam = Math.max(diam, d2);
	}

//----------------- just for validation ------------------

	/**
	 * Strict scanner to veryfy 100% correspondence between input files and input file format specification.
	 * It is a drop-in replacement for {@link java.util.Scanner} that could be added to a soulution source
	 * (cut-and-paste) without breaking its ability to work with {@link java.util.Scanner}.
	 */
	public class XScanner {
		private final BufferedReader in;
		private String line = "";
		private int pos;
		private int lineNo;
		private boolean localeset;

		public XScanner(File source) throws FileNotFoundException {
			in = new BufferedReader(new InputStreamReader(System.in));
			nextLine();
		}

		public void close() {
			assert line == null : "Extra data at the end of file";
			try {
				in.close();
			} catch (IOException e) {
				throw new AssertionError("Failed to close with " + e);
			}
		}

		public void nextLine() {
			assert line != null : "EOF";
			assert pos == line.length() : "Extra characters on line " + lineNo;
			try {
				line = in.readLine();
			} catch (IOException e) {
				throw new AssertionError("Failed to read line with " + e);
			}
			pos = 0;
			lineNo++;
		}

		public String next() {
			assert line != null : "EOF";
			assert line.length() > 0 : "Empty line " + lineNo;
			if (pos == 0)
				assert line.charAt(0) > ' ' : "Line " + lineNo + " starts with whitespace";
			else {
				assert pos < line.length() : "Line " + lineNo + " is over";
				assert line.charAt(pos) == ' ' : "Wrong whitespace on line " + lineNo;
				pos++;
				assert pos < line.length() : "Line " + lineNo + " is over";
				assert line.charAt(0) > ' ' : "Line " + lineNo + " has double whitespace";
			}
			StringBuilder sb = new StringBuilder();
			while (pos < line.length() && line.charAt(pos) > ' ')
				sb.append(line.charAt(pos++));
			return sb.toString();
		}

		public int nextInt() {
			String s = next();
			assert s.length() == 1 || s.charAt(0) != '0' : "Extra leading zero in number " + s + " on line " + lineNo;
			assert s.charAt(0) != '+' : "Extra leading '+' in number " + s + " on line " + lineNo;
			try {
				return Integer.parseInt(s);
			} catch (NumberFormatException e) {
				throw new AssertionError("Malformed number " + s + " on line " + lineNo);
			}
		}

		public double nextDouble() {
			assert localeset : "Locale must be set with useLocale(Locale.US)";
			String s = next();
			assert s.length() == 1 || s.startsWith("0.") || s.charAt(0) != '0' : "Extra leading zero in number " + s + " on line " + lineNo;
			assert s.charAt(0) != '+' : "Extra leading '+' in number " + s + " on line " + lineNo;
			try {
				return Double.parseDouble(s);
			} catch (NumberFormatException e) {
				throw new AssertionError("Malformed number " + s + " on line " + lineNo);
			}
		}

		public void useLocale(Locale locale) {
			assert locale == Locale.US;
			localeset = true;
		}
	}
}
