use std::fmt;
use std::fmt::Debug;
use std::str::FromStr;

pub struct TokenReader {
    reader: std::io::Stdin,
    tokens: Vec<String>,
    index: usize,
}

impl TokenReader {
    pub fn new() -> Self {
        Self {
            reader: std::io::stdin(),
            tokens: Vec::new(),
            index: 0,
        }
    }

    pub fn next<T>(&mut self) -> T
    where
        T: FromStr,
        T::Err: Debug,
    {
        if self.index >= self.tokens.len() {
            self.load_next_line();
        }
        self.index += 1;
        self.tokens[self.index - 1].parse().unwrap()
    }

    pub fn vector<T>(&mut self) -> Vec<T>
    where
        T: FromStr,
        T::Err: Debug,
    {
        if self.index >= self.tokens.len() {
            self.load_next_line();
        }
        self.index = self.tokens.len();
        self.tokens.iter().map(|tok| tok.parse().unwrap()).collect()
    }

    pub fn load_next_line(&mut self) {
        let mut line = String::new();
        self.reader.read_line(&mut line).unwrap();

        self.tokens = line.split_whitespace().map(String::from).collect();
        self.index = 0;
    }
}

#[derive(Clone)]
struct Node {
    edges: Vec<usize>,
}

impl Node {
    fn new() -> Self {
        Self { edges: vec![] }
    }
}

type Graph = Vec<Node>;

enum Res {
    Set(Vec<usize>),
    Cycle(Vec<usize>),
}

impl fmt::Display for Res {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let nodes = match self {
            Res::Set(nodes) => {
                write!(f, "1\n")?;
                nodes
            }
            Res::Cycle(nodes) => {
                write!(f, "2\n{}\n", nodes.len())?;
                nodes
            }
        };
        for &num in nodes.iter() {
            write!(f, "{} ", num + 1)?;
        }
        Ok(())
    }
}

fn extract_set(stack: &Vec<usize>) -> Vec<usize> {
    (0..stack.len()).step_by(2).map(|i| stack[i]).collect()
}

fn extract_cycle(stack: &Vec<usize>, start: usize) -> Vec<usize> {
    let start = stack.iter().position(|&node| node == start).unwrap();
    stack[start..].to_vec()
}

fn connected(graph: &Graph, a: usize, b: usize) -> bool {
    graph[a].edges.iter().any(|&x| x == b)
}

fn dfs(graph: &Graph, k: usize, stack: &mut Vec<usize>, in_stack: &mut Vec<bool>) -> Option<Res> {
    if stack.len() > k {
        return None;
    }

    let node = stack[stack.len() - 1];
    for &next in graph[node].edges.iter() {
        if stack.len() > 1 && next == stack[stack.len() - 2] {
            continue;
        }

        if in_stack[next] {
            return Some(Res::Cycle(extract_cycle(&stack, next)));
        } else {
            if stack.len() % 2 == 0 {
                let mut valid = true;
                for j in (0..stack.len()).step_by(2) {
                    if connected(&graph, stack[j], next) {
                        valid = false;
                        break;
                    }
                }
                if !valid {
                    continue;
                }
            }

            stack.push(next);
            in_stack[next] = true;

            if (stack.len() + 1) / 2 >= (k + 1) / 2 {
                if !connected(&graph, stack[0], stack[stack.len() - 1]) {
                    return Some(Res::Set(extract_set(&stack)));
                }
            }

            let res = dfs(graph, k, stack, in_stack);
            if res.is_some() {
                return res;
            } else {
                stack.pop();
                in_stack[next] = false;
            }
        }
    }
    None
}

fn is_tree(graph: &Graph) -> bool {
    let mut father = vec![None; graph.len()];
    let mut visited = vec![false; graph.len()];

    use std::collections::VecDeque;
    let mut queue = VecDeque::new();

    queue.push_back(0);
    visited[0] = true;

    while let Some(node) = queue.pop_front() {
        for &next in graph[node].edges.iter() {
            if father[node].is_some() && next == father[node].unwrap() {
                continue;
            }
            if visited[next] {
                return false;
            } else {
                father[next] = Some(node);
                visited[next] = true;
                queue.push_back(next);
            }
        }
    }
    visited.iter().all(|&b| b)
}

fn tree_set(graph: &Graph, k: usize) -> Vec<usize> {
    use std::collections::VecDeque;
    let mut queue = VecDeque::new();
    let mut color = vec![0; graph.len()];
    let mut count = vec![1, 0];
    let mut visited = vec![false; graph.len()];

    visited[0] = true;
    color[0] = 1;
    count[color[0]] = 1;
    queue.push_back(0);

    while let Some(node) = queue.pop_front() {
        for &next in graph[node].edges.iter() {
            if !visited[next] {
                visited[next] = true;
                color[next] = 1 - color[node];
                count[color[next]] += 1;
                queue.push_back(next);
            }
        }
    }

    let best = if count[0] > count[1] { 0 } else { 1 };
    (0..graph.len())
        .filter(|&node| color[node] == best)
        .take(k)
        .collect()
}

fn solve(graph: &mut Graph, k: usize) -> Res {
    if is_tree(&graph) {
        return Res::Set(tree_set(&graph, (k + 1) / 2));
    }

    for node in 0..graph.len() {
        graph[node].edges.sort();
    }

    for i in 0..graph.len() {
        let mut stack = vec![i];
        let mut in_stack = vec![false; graph.len()];
        in_stack[i] = true;

        let maybe_res = dfs(&graph, k, &mut stack, &mut in_stack);
        if let Some(res) = maybe_res {
            return res;
        }
    }
    Res::Cycle(vec![])
}

fn main() {
    let mut reader = TokenReader::new();
    let nodes = reader.next();
    let edges = reader.next();
    let k = reader.next();

    let mut graph = vec![Node::new(); nodes];
    for _ in 0..edges {
        let a = reader.next::<usize>();
        let b = reader.next::<usize>();
        graph[a - 1].edges.push(b - 1);
        graph[b - 1].edges.push(a - 1);
    }

    let res = solve(&mut graph, k);
    println!("{}", res);
}


use std::fmt;
use std::fmt::Debug;
use std::str::FromStr;

pub struct TokenReader {
    reader: std::io::Stdin,
    tokens: Vec<String>,
    index: usize,
}

impl TokenReader {
    pub fn new() -> Self {
        Self {
            reader: std::io::stdin(),
            tokens: Vec::new(),
            index: 0,
        }
    }

    pub fn next<T>(&mut self) -> T
    where
        T: FromStr,
        T::Err: Debug,
    {
        if self.index >= self.tokens.len() {
            self.load_next_line();
        }
        self.index += 1;
        self.tokens[self.index - 1].parse().unwrap()
    }

    pub fn vector<T>(&mut self) -> Vec<T>
    where
        T: FromStr,
        T::Err: Debug,
    {
        if self.index >= self.tokens.len() {
            self.load_next_line();
        }
        self.index = self.tokens.len();
        self.tokens.iter().map(|tok| tok.parse().unwrap()).collect()
    }

    pub fn load_next_line(&mut self) {
        let mut line = String::new();
        self.reader.read_line(&mut line).unwrap();

        self.tokens = line.split_whitespace().map(String::from).collect();
        self.index = 0;
    }
}

#[derive(Clone)]
struct Node {
    edges: Vec<usize>,
}

impl Node {
    fn new() -> Self {
        Self { edges: vec![] }
    }
}

type Graph = Vec<Node>;

enum Res {
    Set(Vec<usize>),
    Cycle(Vec<usize>),
}

impl fmt::Display for Res {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let nodes = match self {
            Res::Set(nodes) => {
                write!(f, "1\n")?;
                nodes
            }
            Res::Cycle(nodes) => {
                write!(f, "2\n{}\n", nodes.len())?;
                nodes
            }
        };
        for &num in nodes.iter() {
            write!(f, "{} ", num + 1)?;
        }
        Ok(())
    }
}

fn extract_set(stack: &Vec<usize>) -> Vec<usize> {
    (0..stack.len()).step_by(2).map(|i| stack[i]).collect()
}

fn extract_cycle(stack: &Vec<usize>, start: usize) -> Vec<usize> {
    let start = stack.iter().position(|&node| node == start).unwrap();
    stack[start..].to_vec()
}

fn connected(graph: &Graph, a: usize, b: usize) -> bool {
    graph[a].edges.iter().any(|&x| x == b)
}

fn dfs(graph: &Graph, k: usize, stack: &mut Vec<usize>, in_stack: &mut Vec<bool>) -> Option<Res> {
    if stack.len() > k {
        return None;
    }

    let node = stack[stack.len() - 1];
    for &next in graph[node].edges.iter() {
        if stack.len() > 1 && next == stack[stack.len() - 2] {
            continue;
        }

        if in_stack[next] {
            return Some(Res::Cycle(extract_cycle(&stack, next)));
        } else {
            for j in (0..stack.len() - 1).step_by(2) {
                if connected(&graph, stack[j], next) {
                    stack.push(next);
                    return Some(Res::Cycle(extract_cycle(&stack, stack[j])));
                }
            }

            stack.push(next);
            in_stack[next] = true;

            if (stack.len() + 1) / 2 >= (k + 1) / 2 {
                if !connected(&graph, stack[0], stack[stack.len() - 1]) {
                    return Some(Res::Set(extract_set(&stack)));
                }
            }

            let res = dfs(graph, k, stack, in_stack);
            if res.is_some() {
                return res;
            } else {
                stack.pop();
                in_stack[next] = false;
            }
        }
    }
    None
}

fn is_tree(graph: &Graph) -> bool {
    let mut father = vec![None; graph.len()];
    let mut visited = vec![false; graph.len()];

    use std::collections::VecDeque;
    let mut queue = VecDeque::new();

    queue.push_back(0);
    visited[0] = true;

    while let Some(node) = queue.pop_front() {
        for &next in graph[node].edges.iter() {
            if father[node].is_some() && next == father[node].unwrap() {
                continue;
            }
            if visited[next] {
                return false;
            } else {
                father[next] = Some(node);
                visited[next] = true;
                queue.push_back(next);
            }
        }
    }
    visited.iter().all(|&b| b)
}

fn tree_set(graph: &Graph, k: usize) -> Vec<usize> {
    use std::collections::VecDeque;
    let mut queue = VecDeque::new();
    let mut color = vec![0; graph.len()];
    let mut count = vec![1, 0];
    let mut visited = vec![false; graph.len()];

    visited[0] = true;
    color[0] = 1;
    count[color[0]] = 1;
    queue.push_back(0);

    while let Some(node) = queue.pop_front() {
        for &next in graph[node].edges.iter() {
            if !visited[next] {
                visited[next] = true;
                color[next] = 1 - color[node];
                count[color[next]] += 1;
                queue.push_back(next);
            }
        }
    }

    let best = if count[0] > count[1] { 0 } else { 1 };
    (0..graph.len())
        .filter(|&node| color[node] == best)
        .take(k)
        .collect()
}

fn solve(graph: &mut Graph, k: usize) -> Res {
    if is_tree(&graph) {
        return Res::Set(tree_set(&graph, (k + 1) / 2));
    }

    for node in 0..graph.len() {
        graph[node].edges.sort();
    }

    for i in 0..graph.len() {
        let mut stack = vec![i];
        let mut in_stack = vec![false; graph.len()];
        in_stack[i] = true;

        let maybe_res = dfs(&graph, k, &mut stack, &mut in_stack);
        if let Some(res) = maybe_res {
            return res;
        }
    }
    Res::Cycle(vec![])
}

fn main() {
    let mut reader = TokenReader::new();
    let nodes = reader.next();
    let edges = reader.next();
    let k = reader.next();

    let mut graph = vec![Node::new(); nodes];
    for _ in 0..edges {
        let a = reader.next::<usize>();
        let b = reader.next::<usize>();
        graph[a - 1].edges.push(b - 1);
        graph[b - 1].edges.push(a - 1);
    }

    let res = solve(&mut graph, k);
    println!("{}", res);
}


//spnauti-rusT {{{
use std::io::*; use std::str::{self,*}; use std::fmt::Debug;
#[allow(unused_imports)] use std::cmp::Ordering::{self,*};
#[allow(unused_imports)] use std::ops::{self,*};
#[allow(unused_imports)] use std::iter::{self,*};
#[allow(unused_imports)] use std::collections::*;
#[allow(unused_imports)] use std::cell::*;
#[allow(unused_macros)] macro_rules! min {
	($x:expr,$y:expr) => {{ let b=$y; let a=&mut$x; if b < *a {*a=b; true} else {false} }};
}
#[allow(unused_macros)] macro_rules! max {
	($x:expr,$y:expr) => {{ let b=$y; let a=&mut$x; if b > *a {*a=b; true} else {false} }};
}
#[allow(unused_macros)] macro_rules! l {
	($($v:ident),+ =$e:expr) => {$(let$v=$e;)+};
	($($v:ident),+:$t:ty=$e:expr) => {$(let$v:$t=$e;)+};
	(mut $($v:ident),+ =$e:expr) => {$(let mut$v=$e;)+};
	(mut $($v:ident),+:$t:ty=$e:expr) => {$(let mut$v:$t=$e;)+};
}
#[allow(unused_macros)] macro_rules! v {
	([$d:expr]$($s:tt)+) => {vec![v!($($s)+);$d]};
	([]) => {Vec::new()}; ([$e:expr]) => {Vec::with_capacity($e)}; (=$e:expr) => {$e};
}
#[allow(unused_macros)] macro_rules! rep { {[$c:expr]$($s:tt)+} => {for _ in 0..$c {$($s)+}} }
#[allow(dead_code)] fn reader() -> WordReaderC { WordReaderC::new() }
#[allow(dead_code)] fn writer() -> BufWriter<Stdout> { BufWriter::new(stdout()) }
struct WordReaderC {buf: Vec<u8>, pos: usize, q: std::io::StdinLock<'static>}//'
#[allow(dead_code)] impl WordReaderC {
	fn new() -> Self {
		let r = unsafe {&*Box::into_raw(Box::new(stdin()))};
		Self { q: r.lock(), buf: v!([]), pos: 0 }
	}
	fn next_line(&mut self) -> bool {
		self.buf.clear(); self.pos = 0;
		self.q.read_until(b'\n', &mut self.buf).unwrap_or(0) > 0
	}
	fn is_ws(c: u8) -> bool {
		c == b' ' || c == b'\r' || c == b'\n' || c == b'\t'
	}
	fn byte(&mut self) -> Option<u8> {
		if self.pos == self.buf.len() { if !self.next_line() { return None; } }
		self.pos += 1; Some(self.buf[self.pos - 1])
	}
	fn vb(&mut self) -> Vec<u8> {
		let mut s = v!([8]);
		let mut f = false;
		loop {
			if let Some(c) = self.byte() {
				if !Self::is_ws(c) {
					s.push(c);
					f = true;
				} else if f { break; }
			} else { break; }
		}
		s
	}
	fn vvb(&mut self, r: usize, c: Option<usize>) -> Vec<Vec<u8>> {
		let mut res = v!([r]);
		let c = c.unwrap_or(0);
		rep!{[r]
			let t = self.vb();
			assert!(c == 0 || t.len() == c);
			res.push(t);
		}
		res
	}
	fn s(&mut self) -> String { String::from_utf8(self.vb()).expect("invalid utf8") }
	fn i(&mut self) ->    i32 { self.p() }
	fn l(&mut self) ->    i64 { self.p() }
	fn u(&mut self) ->  usize { self.p() }
	fn f(&mut self) ->    f64 { self.p() }
	fn vi(&mut self, n: usize) -> Vec<i32> { self.vp(n) }
	fn vl(&mut self, n: usize) -> Vec<i64> { self.vp(n) }
	fn vu(&mut self, n: usize) -> Vec<usize> { self.vp(n) }
	fn ii(&mut self, n: usize) -> impl Iterator<Item=i32> { self.ip(n).into_iter() }
	fn iu(&mut self, n: usize) -> impl Iterator<Item=usize> { self.ip(n).into_iter() }
	fn p<T: FromStr>(&mut self) -> T where T::Err: Debug {
		let w = self.vb(); str::from_utf8(w.as_ref()).unwrap().parse::<T>().unwrap()
	}
	fn vp<T: FromStr>(&mut self, n: usize) -> Vec<T> where T::Err: Debug {
		(0..n).map(|_|self.p()).collect()
	}
	fn ip<T: FromStr>(&mut self, n: usize) -> impl Iterator<Item=T> where T::Err: Debug {
		self.vp(n).into_iter()
	}
	fn graph(&mut self, n: usize, m: usize) -> Vec<Vec<usize>> {
		let mut e = v!([n][]); rep!{[m] l!(a,b = self.u()-1); e[a].push(b); e[b].push(a); } e
	}
	fn graph_w<T: Copy+FromStr>(&mut self, n: usize, m: usize) -> Vec<Vec<(usize,T)>> where T::Err: Debug {
		let mut e = v!([n][]); rep!{[m] l!(a,b = self.u()-1); let c: T = self.p(); e[a].push((b,c)); e[b].push((a,c)); } e
	}
}
//------------------- End rusT }}}

fn dfs(u: usize, e: &[Vec<usize>], vis: &mut [bool], vert: &mut Vec<usize>) {
	vis[u] = true;
	vert.push(u);
	for &v in e[u].iter() {
		if !vis[v] {
			dfs(v, e, vis, vert);
		}
	}
}

fn dfs_cycle(u: usize, par: usize, e: &[Vec<usize>], vis: &mut [u8], cycle: &mut Vec<usize>) -> u8 {
	vis[u] = 1;
	for &v in e[u].iter() {
		if v != par {
			if vis[v] == 1 {
				cycle.push(v);
				cycle.push(u);
				return 1;
			}
			if vis[v] == 0 {
				let res = dfs_cycle(v, u, e, vis, cycle);
				if res == 1 {
					if cycle[0] == u {
						return 2;
					}
					cycle.push(u);
					return 1;
				}
				if res == 2 {
					return 2;
				}
			}
		}
	}
	vis[u] = 2;
	0
}

fn dfs_is(u: usize, par: usize, e: &[Vec<usize>], dp: &mut [(usize,usize)]) {
	let mut a = 0;
	let mut b = 1;
	for &v in e[u].iter() {
		if v != par {
			dfs_is(v, u, e, dp);
			let (aa,bb) = dp[v];
			a += aa.max(bb);
			b += aa;
		}
	}
	dp[u] = (a,b);
}

fn dfs_is2(u: usize, par: usize, e: &[Vec<usize>], dp: &[(usize,usize)], w: usize, is: &mut Vec<usize>) {
	if w == 1 {
		is.push(u);
	}
	for &v in e[u].iter() {
		if v != par {
			let ww = if w == 1 || dp[v].0 >= dp[v].1 {
				0
			} else {
				1
			};
			dfs_is2(v, u, e, dp, ww, is);
		}
	}
}

fn main() {
	let mut rin = reader();
	let mut rout = writer();

	l!(n,m,k = rin.u());
	let e = rin.graph(n,m);
	let mut vert = v!([n]);
	let mut vis = v!([n] = false);
	dfs(0, &e, &mut vis, &mut vert);
	vert.truncate(k);
	let mut used = v!([n] = false);
	for &v in vert.iter() {
		used[v] = true;
	}
	let mut ee = v!([n][]);
	let mut mm = 0;
	for u in 0..n {
		if used[u] {
			for &v in e[u].iter() {
				if u < v && used[v] {
					ee[u].push(v);
					ee[v].push(u);
					mm += 1;
				}
			}
		}
	}
	if mm >= k {
		let mut cycle = v!([k]);
		let mut vis = v!([n] = 0);
		dfs_cycle(vert[0], n, &ee, &mut vis, &mut cycle);
		writeln!(rout, "2").ok();
		writeln!(rout, "{}", cycle.len()).ok();
		for u in cycle {
			write!(rout, "{} ", u+1).ok();
		}
	} else {
		let mut dp = v!([n] = (0,0));
		dfs_is(vert[0], n, &ee, &mut dp);
		let mut is = v!([]);
		let w = if dp[vert[0]].0 >= dp[vert[0]].1 {0} else {1};
		dfs_is2(vert[0], n, &ee, &dp, w, &mut is);
		is.truncate((k+1) / 2);
		writeln!(rout, "1").ok();
		for u in is {
			write!(rout, "{} ", u+1).ok();
		}
	}
}


// SNIPPET read
pub trait Readable {
    type Output;
    const WORD_COUNT: usize;
    fn read_words(words: &[&str]) -> Result<Self::Output, String>;
}
#[macro_export]
macro_rules! readable {
    ( $t:ty, $words_count:expr, |$words:ident| $read_words:expr ) => {
        impl Readable for $t {
            type Output = $t;
            const WORD_COUNT: usize = $words_count;
            fn read_words($words: &[&str]) -> Result<$t, String> {
                Ok($read_words)
            }
        }
    };
}
readable!((), 1, |_ss| ());
readable!(String, 1, |ss| ss[0].to_string());
impl Readable for char {
    type Output = char;
    const WORD_COUNT: usize = 1;
    fn read_words(words: &[&str]) -> Result<char, String> {
        let chars: Vec<char> = words[0].chars().collect();
        if chars.len() == 1 {
            Ok(chars[0])
        } else {
            Err(format!("cannot parse `{}` as a char", words[0]))
        }
    }
}
pub struct Chars();
impl Readable for Chars {
    type Output = Vec<char>;
    const WORD_COUNT: usize = 1;
    fn read_words(words: &[&str]) -> Result<Vec<char>, String> {
        Ok(words[0].chars().collect())
    }
}
pub struct Bytes();
impl Readable for Bytes {
    type Output = Vec<u8>;
    const WORD_COUNT: usize = 1;
    fn read_words(words: &[&str]) -> Result<Vec<u8>, String> {
        Ok(words[0].bytes().collect())
    }
}
impl Readable for i8 {
    type Output = Self;
    const WORD_COUNT: usize = 1;
    fn read_words(words: &[&str]) -> Result<i8, String> {
        use std::str::FromStr;
        i8::from_str(words[0]).map_err(|_| {
            format!("cannot parse `{}` as i8", words[0])
        })
    }
}
impl Readable for u8 {
    type Output = Self;
    const WORD_COUNT: usize = 1;
    fn read_words(words: &[&str]) -> Result<u8, String> {
        use std::str::FromStr;
        u8::from_str(words[0]).map_err(|_| {
            format!("cannot parse `{}` as u8", words[0])
        })
    }
}
impl Readable for i16 {
    type Output = Self;
    const WORD_COUNT: usize = 1;
    fn read_words(words: &[&str]) -> Result<i16, String> {
        use std::str::FromStr;
        i16::from_str(words[0]).map_err(|_| {
            format!("cannot parse `{}` as i16", words[0])
        })
    }
}
impl Readable for u16 {
    type Output = Self;
    const WORD_COUNT: usize = 1;
    fn read_words(words: &[&str]) -> Result<u16, String> {
        use std::str::FromStr;
        u16::from_str(words[0]).map_err(|_| {
            format!("cannot parse `{}` as u16", words[0])
        })
    }
}
impl Readable for i32 {
    type Output = Self;
    const WORD_COUNT: usize = 1;
    fn read_words(words: &[&str]) -> Result<i32, String> {
        use std::str::FromStr;
        i32::from_str(words[0]).map_err(|_| {
            format!("cannot parse `{}` as i32", words[0])
        })
    }
}
impl Readable for u32 {
    type Output = Self;
    const WORD_COUNT: usize = 1;
    fn read_words(words: &[&str]) -> Result<u32, String> {
        use std::str::FromStr;
        u32::from_str(words[0]).map_err(|_| {
            format!("cannot parse `{}` as u32", words[0])
        })
    }
}
impl Readable for i64 {
    type Output = Self;
    const WORD_COUNT: usize = 1;
    fn read_words(words: &[&str]) -> Result<i64, String> {
        use std::str::FromStr;
        i64::from_str(words[0]).map_err(|_| {
            format!("cannot parse `{}` as i64", words[0])
        })
    }
}
impl Readable for u64 {
    type Output = Self;
    const WORD_COUNT: usize = 1;
    fn read_words(words: &[&str]) -> Result<u64, String> {
        use std::str::FromStr;
        u64::from_str(words[0]).map_err(|_| {
            format!("cannot parse `{}` as u64", words[0])
        })
    }
}
impl Readable for isize {
    type Output = Self;
    const WORD_COUNT: usize = 1;
    fn read_words(words: &[&str]) -> Result<isize, String> {
        use std::str::FromStr;
        <isize>::from_str(words[0]).map_err(|_| {
            format!("cannot parse `{}` as isize", words[0])
        })
    }
}
impl Readable for usize {
    type Output = Self;
    const WORD_COUNT: usize = 1;
    fn read_words(words: &[&str]) -> Result<usize, String> {
        use std::str::FromStr;
        <usize>::from_str(words[0]).map_err(|_| {
            format!("cannot parse `{}` as usize", words[0])
        })
    }
}
impl Readable for f32 {
    type Output = Self;
    const WORD_COUNT: usize = 1;
    fn read_words(words: &[&str]) -> Result<f32, String> {
        use std::str::FromStr;
        f32::from_str(words[0]).map_err(|_| {
            format!("cannot parse `{}` as f32", words[0])
        })
    }
}
impl Readable for f64 {
    type Output = Self;
    const WORD_COUNT: usize = 1;
    fn read_words(words: &[&str]) -> Result<f64, String> {
        use std::str::FromStr;
        f64::from_str(words[0]).map_err(|_| {
            format!("cannot parse `{}` as f64", words[0])
        })
    }
}
#[allow(non_camel_case_types)]
pub struct u8_;
impl Readable for u8_ {
    type Output = u8;
    const WORD_COUNT: usize = 1;
    fn read_words(words: &[&str]) -> Result<Self::Output, String> {
        u8::read_words(words).map(|n| n-1)
    }
}
#[allow(non_camel_case_types)]
pub struct u16_;
impl Readable for u16_ {
    type Output = u16;
    const WORD_COUNT: usize = 1;
    fn read_words(words: &[&str]) -> Result<Self::Output, String> {
        u16::read_words(words).map(|n| n-1)
    }
}
#[allow(non_camel_case_types)]
pub struct u32_;
impl Readable for u32_ {
    type Output = u32;
    const WORD_COUNT: usize = 1;
    fn read_words(words: &[&str]) -> Result<Self::Output, String> {
        u32::read_words(words).map(|n| n-1)
    }
}
#[allow(non_camel_case_types)]
pub struct u64_;
impl Readable for u64_ {
    type Output = u64;
    const WORD_COUNT: usize = 1;
    fn read_words(words: &[&str]) -> Result<Self::Output, String> {
        u64::read_words(words).map(|n| n-1)
    }
}
#[allow(non_camel_case_types)]
pub struct usize_;
impl Readable for usize_ {
    type Output = usize;
    const WORD_COUNT: usize = 1;
    fn read_words(words: &[&str]) -> Result<Self::Output, String> {
        <usize>::read_words(words).map(|n| n-1)
    }
}
impl<T1: Readable, T2: Readable> Readable for (T1, T2) {
    type Output = (T1::Output, T2::Output);
    const WORD_COUNT: usize = T1::WORD_COUNT + T2::WORD_COUNT;
    fn read_words(words: &[&str]) -> Result<Self::Output, String> {
        assert_eq!(words.len(), Self::WORD_COUNT);
        let mut start = 0;
        let count1 = T1::WORD_COUNT;
        let val1 = T1::read_words(&words[start .. start+count1])?;
        start += count1;
        let val2 = T2::read_words(&words[start..])?;
        Ok((val1, val2))
    }
}
impl<T1: Readable, T2: Readable, T3: Readable> Readable for (T1, T2, T3) {
    type Output = (T1::Output, T2::Output, T3::Output);
    const WORD_COUNT: usize = T1::WORD_COUNT + T2::WORD_COUNT + T3::WORD_COUNT;
    fn read_words(words: &[&str]) -> Result<Self::Output, String> {
        assert_eq!(words.len(), Self::WORD_COUNT);
        let mut start = 0;
        let count1 = T1::WORD_COUNT;
        let val1 = T1::read_words(&words[start .. start+count1])?;
        start += count1;
        let count2 = T2::WORD_COUNT;
        let val2 = T2::read_words(&words[start .. start+count2])?;
        start += count2;
        let val3 = T3::read_words(&words[start..])?;
        Ok((val1, val2, val3))
    }
}
impl<T1: Readable, T2: Readable, T3: Readable, T4: Readable> Readable for (T1, T2, T3, T4) {
    type Output = (T1::Output, T2::Output, T3::Output, T4::Output);
    const WORD_COUNT: usize = T1::WORD_COUNT + T2::WORD_COUNT + T3::WORD_COUNT + T4::WORD_COUNT;
    fn read_words(words: &[&str]) -> Result<Self::Output, String> {
        assert_eq!(words.len(), Self::WORD_COUNT);
        let mut start = 0;
        let count1 = T1::WORD_COUNT;
        let val1 = T1::read_words(&words[start .. start+count1])?;
        start += count1;
        let count2 = T2::WORD_COUNT;
        let val2 = T2::read_words(&words[start .. start+count2])?;
        start += count2;
        let count3 = T3::WORD_COUNT;
        let val3 = T3::read_words(&words[start .. start+count3])?;
        start += count3;
        let val4 = T4::read_words(&words[start..])?;
        Ok((val1, val2, val3, val4))
    }
}
impl<T1: Readable, T2: Readable, T3: Readable, T4: Readable, T5: Readable> Readable for (T1, T2, T3, T4, T5) {
    type Output = (T1::Output, T2::Output, T3::Output, T4::Output, T5::Output);
    const WORD_COUNT: usize = T1::WORD_COUNT + T2::WORD_COUNT + T3::WORD_COUNT + T4::WORD_COUNT + T5::WORD_COUNT;
    fn read_words(words: &[&str]) -> Result<Self::Output, String> {
        assert_eq!(words.len(), Self::WORD_COUNT);
        let mut start = 0;
        let count1 = T1::WORD_COUNT;
        let val1 = T1::read_words(&words[start .. start+count1])?;
        start += count1;
        let count2 = T2::WORD_COUNT;
        let val2 = T2::read_words(&words[start .. start+count2])?;
        start += count2;
        let count3 = T3::WORD_COUNT;
        let val3 = T3::read_words(&words[start .. start+count3])?;
        start += count3;
        let count4 = T4::WORD_COUNT;
        let val4 = T4::read_words(&words[start .. start+count4])?;
        start += count4;
        let val5 = T5::read_words(&words[start..])?;
        Ok((val1, val2, val3, val4, val5))
    }
}
impl<T1: Readable, T2: Readable, T3: Readable, T4: Readable, T5: Readable, T6: Readable> Readable for (T1, T2, T3, T4, T5, T6) {
    type Output = (T1::Output, T2::Output, T3::Output, T4::Output, T5::Output, T6::Output);
    const WORD_COUNT: usize = T1::WORD_COUNT + T2::WORD_COUNT + T3::WORD_COUNT + T4::WORD_COUNT + T5::WORD_COUNT + T6::WORD_COUNT;
    fn read_words(words: &[&str]) -> Result<Self::Output, String> {
        assert_eq!(words.len(), Self::WORD_COUNT);
        let mut start = 0;
        let count1 = T1::WORD_COUNT;
        let val1 = T1::read_words(&words[start .. start+count1])?;
        start += count1;
        let count2 = T2::WORD_COUNT;
        let val2 = T2::read_words(&words[start .. start+count2])?;
        start += count2;
        let count3 = T3::WORD_COUNT;
        let val3 = T3::read_words(&words[start .. start+count3])?;
        start += count3;
        let count4 = T4::WORD_COUNT;
        let val4 = T4::read_words(&words[start .. start+count4])?;
        start += count4;
        let count5 = T5::WORD_COUNT;
        let val5 = T5::read_words(&words[start .. start+count5])?;
        start += count5;
        let val6 = T6::read_words(&words[start..])?;
        Ok((val1, val2, val3, val4, val5, val6))
    }
}
impl<T: Readable> Readable for [T; 2] {
    type Output = [T::Output; 2];
    const WORD_COUNT: usize = T::WORD_COUNT * 2;
    fn read_words(words: &[&str]) -> Result<Self::Output, String> {
        assert_eq!(words.len(), Self::WORD_COUNT);
        let val1 = T::read_words(&words[T::WORD_COUNT*0 .. T::WORD_COUNT*1])?;
        let val2 = T::read_words(&words[T::WORD_COUNT*1 .. T::WORD_COUNT*2])?;
        Ok([val1, val2])
    }
}
impl<T: Readable> Readable for [T; 3] {
    type Output = [T::Output; 3];
    const WORD_COUNT: usize = T::WORD_COUNT * 3;
    fn read_words(words: &[&str]) -> Result<Self::Output, String> {
        assert_eq!(words.len(), Self::WORD_COUNT);
        let val1 = T::read_words(&words[T::WORD_COUNT*0 .. T::WORD_COUNT*1])?;
        let val2 = T::read_words(&words[T::WORD_COUNT*1 .. T::WORD_COUNT*2])?;
        let val3 = T::read_words(&words[T::WORD_COUNT*2 .. T::WORD_COUNT*3])?;
        Ok([val1, val2, val3])
    }
}
impl<T: Readable> Readable for [T; 4] {
    type Output = [T::Output; 4];
    const WORD_COUNT: usize = T::WORD_COUNT * 4;
    fn read_words(words: &[&str]) -> Result<Self::Output, String> {
        assert_eq!(words.len(), Self::WORD_COUNT);
        let val1 = T::read_words(&words[T::WORD_COUNT*0 .. T::WORD_COUNT*1])?;
        let val2 = T::read_words(&words[T::WORD_COUNT*1 .. T::WORD_COUNT*2])?;
        let val3 = T::read_words(&words[T::WORD_COUNT*2 .. T::WORD_COUNT*3])?;
        let val4 = T::read_words(&words[T::WORD_COUNT*3 .. T::WORD_COUNT*4])?;
        Ok([val1, val2, val3, val4])
    }
}
impl<T: Readable> Readable for [T; 5] {
    type Output = [T::Output; 5];
    const WORD_COUNT: usize = T::WORD_COUNT * 5;
    fn read_words(words: &[&str]) -> Result<Self::Output, String> {
        assert_eq!(words.len(), Self::WORD_COUNT);
        let val1 = T::read_words(&words[T::WORD_COUNT*0 .. T::WORD_COUNT*1])?;
        let val2 = T::read_words(&words[T::WORD_COUNT*1 .. T::WORD_COUNT*2])?;
        let val3 = T::read_words(&words[T::WORD_COUNT*2 .. T::WORD_COUNT*3])?;
        let val4 = T::read_words(&words[T::WORD_COUNT*3 .. T::WORD_COUNT*4])?;
        let val5 = T::read_words(&words[T::WORD_COUNT*4 .. T::WORD_COUNT*5])?;
        Ok([val1, val2, val3, val4, val5])
    }
}
impl<T: Readable> Readable for [T; 6] {
    type Output = [T::Output; 6];
    const WORD_COUNT: usize = T::WORD_COUNT * 6;
    fn read_words(words: &[&str]) -> Result<Self::Output, String> {
        assert_eq!(words.len(), Self::WORD_COUNT);
        let val1 = T::read_words(&words[T::WORD_COUNT*0 .. T::WORD_COUNT*1])?;
        let val2 = T::read_words(&words[T::WORD_COUNT*1 .. T::WORD_COUNT*2])?;
        let val3 = T::read_words(&words[T::WORD_COUNT*2 .. T::WORD_COUNT*3])?;
        let val4 = T::read_words(&words[T::WORD_COUNT*3 .. T::WORD_COUNT*4])?;
        let val5 = T::read_words(&words[T::WORD_COUNT*4 .. T::WORD_COUNT*5])?;
        let val6 = T::read_words(&words[T::WORD_COUNT*5 .. T::WORD_COUNT*6])?;
        Ok([val1, val2, val3, val4, val5, val6])
    }
}
pub trait ReadableFromLine {
    type Output;
    fn read_line(line: &str) -> Result<Self::Output, String>;
}
fn split_into_words(line: &str) -> Vec<&str> {
    line.trim_end_matches('\n').split_whitespace().collect()
}
impl<T: Readable> ReadableFromLine for T {
    type Output = T::Output;
    fn read_line(line: &str) -> Result<T::Output, String> {
        let words = split_into_words(line);
        if words.len() != T::WORD_COUNT {
            return Err(format!("line `{}` has {} words, expected {}",
                               line, words.len(), T::WORD_COUNT));
        }
        T::read_words(&words)
    }
}
pub fn read_words_into_vec<T: Readable>(words: &[&str], line: &str) -> Result<Vec<T::Output>, String> {
    let n = T::WORD_COUNT;
    assert_eq!(words.len() % n, 0);
    let mut result = Vec::new();
    for chunk in words.chunks(n) {
        match T::read_words(chunk) {
            Ok(v) => result.push(v),
            Err(msg) => {
                let fragment_msg = if n == 1 {
                    format!("word {}", result.len())
                } else {
                    let l = result.len();
                    format!("words {}-{}", n*l + 1, (n+1) * l)
                };
                return Err(format!(
                    "{} of line `{}`: {}", fragment_msg, line, msg
                ));
            }
        }
    }
    Ok(result)
}
pub fn split_into_words_for_collection<T: Readable>(
    line: &str, prefix_words_count: usize
) -> Result<Vec<&str>, String> {
    let n = T::WORD_COUNT;
    let words = split_into_words(line);
    if words.len() < prefix_words_count {
        return Err(
            format!("line `{}` has {} words, expected at least {}",
                    line, words.len(), prefix_words_count)
        );
    }
    if (words.len() - prefix_words_count) % T::WORD_COUNT != 0 {
        return Err(
            format!("line `{}` has {} words, expected {} + {}",
                    line, words.len(), prefix_words_count, n)
        );
    }
    Ok(words)
}
#[macro_export]
macro_rules! readable_collection {
    ($u:ident => $collection_in:ty, $collection_out:ty) => {
        readable_collection!($u: => $collection_in, $collection_out);
    };
    ($u:ident : $( $bound:path ),* => $collection_in:ty, $collection_out:ty) => {
        impl<$u: Readable> ReadableFromLine for $collection_in
        where
            <$u as Readable>::Output: Sized $(+ $bound)*
        {
            type Output = $collection_out;
            fn read_line(line: &str) -> Result<Self::Output, String> {
                let words = split_into_words_for_collection::<$u>(line, 0)?;
                Ok(read_words_into_vec::<$u>(&words, line)?.into_iter().collect())
            }
        }
        impl<T1: Readable, $u: Readable> ReadableFromLine for (T1, $collection_in)
        where
            <$u as Readable>::Output: Sized $(+ $bound)*
        {
            type Output = (T1::Output, $collection_out);
            fn read_line(line: &str) -> Result<Self::Output, String> {
                let prefix_len = T1::WORD_COUNT;
                let words = split_into_words_for_collection::<$u>(line, prefix_len)?;
                let val1 = T1::read_words(&words[..prefix_len])?;
                let rest = read_words_into_vec::<$u>(&words[prefix_len..], line)?;
                Ok((val1, rest.into_iter().collect()))
            }
        }
        impl<T1: Readable, T2: Readable, $u: Readable> ReadableFromLine for (T1, T2, $collection_in)
        where
            <$u as Readable>::Output: Sized $(+ $bound)*
        {
            type Output = (T1::Output, T2::Output, $collection_out);
            fn read_line(line: &str) -> Result<Self::Output, String> {
                let prefix_len = <(T1, T2)>::WORD_COUNT;
                let words = split_into_words_for_collection::<$u>(line, prefix_len)?;
                let mut start = 0;
                let count1 = T1::WORD_COUNT;
                let val1 = T1::read_words(&words[start .. start+count1])?;
                start += count1;
                let count2 = T2::WORD_COUNT;
                let val2 = T2::read_words(&words[start .. start+count2])?;
                let rest = read_words_into_vec::<$u>(&words[prefix_len..], line)?;
                Ok((val1, val2, rest.into_iter().collect()))
            }
        }
        impl<T1: Readable, T2: Readable, T3: Readable, $u: Readable> ReadableFromLine for (T1, T2, T3, $collection_in)
        where
            <$u as Readable>::Output: Sized $(+ $bound)*
        {
            type Output = (T1::Output, T2::Output, T3::Output, $collection_out);
            fn read_line(line: &str) -> Result<Self::Output, String> {
                let prefix_len = <(T1, T2, T3)>::WORD_COUNT;
                let words = split_into_words_for_collection::<$u>(line, prefix_len)?;
                let mut start = 0;
                let count1 = T1::WORD_COUNT;
                let val1 = T1::read_words(&words[start .. start+count1])?;
                start += count1;
                let count2 = T2::WORD_COUNT;
                let val2 = T2::read_words(&words[start .. start+count2])?;
                start += count2;
                let count3 = T3::WORD_COUNT;
                let val3 = T3::read_words(&words[start .. start+count3])?;
                let rest = read_words_into_vec::<$u>(&words[prefix_len..], line)?;
                Ok((val1, val2, val3, rest.into_iter().collect()))
            }
        }
        impl<T1: Readable, T2: Readable, T3: Readable, T4: Readable, $u: Readable> ReadableFromLine for (T1, T2, T3, T4, $collection_in)
        where
            <$u as Readable>::Output: Sized $(+ $bound)*
        {
            type Output = (T1::Output, T2::Output, T3::Output, T4::Output, $collection_out);
            fn read_line(line: &str) -> Result<Self::Output, String> {
                let prefix_len = <(T1, T2, T3, T4)>::WORD_COUNT;
                let words = split_into_words_for_collection::<$u>(line, prefix_len)?;
                let mut start = 0;
                let count1 = T1::WORD_COUNT;
                let val1 = T1::read_words(&words[start .. start+count1])?;
                start += count1;
                let count2 = T2::WORD_COUNT;
                let val2 = T2::read_words(&words[start .. start+count2])?;
                start += count2;
                let count3 = T3::WORD_COUNT;
                let val3 = T3::read_words(&words[start .. start+count3])?;
                start += count3;
                let count4 = T4::WORD_COUNT;
                let val4 = T4::read_words(&words[start .. start+count4])?;
                let rest = read_words_into_vec::<$u>(&words[prefix_len..], line)?;
                Ok((val1, val2, val3, val4, rest.into_iter().collect()))
            }
        }
    };
}
readable_collection!(U => Vec<U>, Vec<U::Output>);
pub fn read<T: ReadableFromLine>() -> T::Output {
    let mut line = String::new();
    std::io::stdin().read_line(&mut line).unwrap();
    T::read_line(&line).unwrap()
}
#[macro_export]
macro_rules! read {
    () => {
        let mut line = String::new();
        std::io::stdin().read_line(&mut line).unwrap();
    };
    ( $pat:pat = $t:ty $(,)* ) => {
        let $pat = read::<$t>();
    };
    ( ! $(,)* ) => {
        let _ = read::<()>();
    };
    ( $pat:pat = $t:ty, $( $rest:tt )+ ) => {
        read_inner!($pat = $t; $($rest)+);
    };
    ( !, $( $rest:tt )+ ) => {
        read_inner!(_ = (); $($rest)+);
    };
}
#[macro_export]
#[doc(hidden)]
macro_rules! read_inner {
    ( $( $acc_pat:pat = $acc_t:ty ),+ ; $pat:pat = $t:ty, $( $rest:tt )* ) => {
        read_inner!($( $acc_pat = $acc_t ),+ , $pat = $t ; $($rest)*);
    };
    ( $( $acc_pat:pat = $acc_t:ty ),+ ; !, $( $rest:tt )* ) => {
        read_inner!($( $acc_pat = $acc_t ),+ , _ = () ; $($rest)*);
    };
    ( $( $acc_pat:pat = $acc_t:ty ),+ ; $pat:pat = $t:ty ) => {
        read_inner!($( $acc_pat = $acc_t ),+ , $pat = $t ;);
    };
    ( $( $acc_pat:pat = $acc_t:ty ),+ ; ! ) => {
        read_inner!($( $acc_pat = $acc_t ),+ , _ = () ;);
    };
    ( $( $pat:pat = $t:ty ),+ ; ) => {
        let ($($pat),+) = read::<($($t),+)>();
    };
}
pub trait ReadableFromChunk {
    type Output;
    fn lines_count() -> usize;
    fn read_chunk(lines: &[String]) -> Result<Self::Output, String>;
}
impl<T1: ReadableFromLine, T2: ReadableFromLine> ReadableFromChunk for (T1, T2) {
    type Output = (T1::Output, T2::Output);
    fn lines_count() -> usize { 2 }
    fn read_chunk(lines: &[String]) -> Result<Self::Output, String> {
        let out1 = T1::read_line(&lines[0])?;
        let out2 = T2::read_line(&lines[1])?;
        Ok((out1, out2))
    }
}
impl<T1: ReadableFromLine, T2: ReadableFromLine, T3: ReadableFromLine> ReadableFromChunk for (T1, T2, T3) {
    type Output = (T1::Output, T2::Output, T3::Output);
    fn lines_count() -> usize { 3 }
    fn read_chunk(lines: &[String]) -> Result<Self::Output, String> {
        let out1 = T1::read_line(&lines[0])?;
        let out2 = T2::read_line(&lines[1])?;
        let out3 = T3::read_line(&lines[2])?;
        Ok((out1, out2, out3))
    }
}
impl<T1: ReadableFromLine, T2: ReadableFromLine, T3: ReadableFromLine, T4: ReadableFromLine> ReadableFromChunk for (T1, T2, T3, T4) {
    type Output = (T1::Output, T2::Output, T3::Output, T4::Output);
    fn lines_count() -> usize { 4 }
    fn read_chunk(lines: &[String]) -> Result<Self::Output, String> {
        let out1 = T1::read_line(&lines[0])?;
        let out2 = T2::read_line(&lines[1])?;
        let out3 = T3::read_line(&lines[2])?;
        let out4 = T4::read_line(&lines[3])?;
        Ok((out1, out2, out3, out4))
    }
}
pub fn read_chunk<T: ReadableFromChunk>() -> T::Output {
    let stdin = std::io::stdin();
    let mut handle = stdin.lock();
    read_chunk_from_handle::<T>(&mut handle).unwrap()
}
fn read_chunk_from_handle<T: ReadableFromChunk>(handle: &mut std::io::StdinLock) -> Option<T::Output> {
    use std::io::BufRead;
    let mut lines = vec![String::new(); T::lines_count()];
    let mut first = true;
    for line in &mut lines {
        if handle.read_line(line).unwrap() == 0 && first {
            return None;
        }
        first = false;
    }
    Some(T::read_chunk(&lines).unwrap())
}
#[macro_export]
macro_rules! read_chunk {
    ( $( $pat:pat = $t:ty ),+ ) => {
        let ($($pat),+) = read_chunk::<($($t),+)>();
    };
}
static mut STDIN: Option<std::io::Stdin> = None;
pub struct ReadLines<T: ReadableFromLine> {
    lock: std::io::StdinLock<'static>,
    phantom: std::marker::PhantomData<T>
}
impl<T: ReadableFromLine> Iterator for ReadLines<T> {
    type Item = T::Output;
    fn next(&mut self) -> Option<T::Output> {
        use std::io::BufRead;
        let mut line = String::new();
        if self.lock.read_line(&mut line).unwrap() > 0 {
            Some(T::read_line(&line).unwrap())
        } else {
            None
        }
    }
}
pub fn read_lines<T: ReadableFromLine>() -> ReadLines<T> {
    unsafe {
        if STDIN.is_none() {
            STDIN = Some(std::io::stdin());
        }
    }
    ReadLines {
        lock: unsafe { STDIN.as_ref().unwrap().lock() },
        phantom: std::marker::PhantomData::<T>
    }
}
pub struct ReadChunks<T: ReadableFromChunk> {
    lock: std::io::StdinLock<'static>,
    phantom: std::marker::PhantomData<T>
}
impl<T: ReadableFromChunk> Iterator for ReadChunks<T> {
    type Item = T::Output;
    fn next(&mut self) -> Option<T::Output> {
        read_chunk_from_handle::<T>(&mut self.lock)
    }
}
pub fn read_chunks<T: ReadableFromChunk>() -> ReadChunks<T> {
    unsafe {
        if STDIN.is_none() {
            STDIN = Some(std::io::stdin());
        }
    }
    ReadChunks {
        lock: unsafe { STDIN.as_ref().unwrap().lock() },
        phantom: std::marker::PhantomData::<T>
    }
}
pub trait Words {
    fn read<T: Readable>(&self) -> T::Output;
}
impl<'a> Words for [&'a str] {
    fn read<T: Readable>(&self) -> T::Output {
        T::read_words(self).unwrap()
    }
}
impl<'a> Words for &'a str {
    fn read<T: Readable>(&self) -> T::Output {
        T::read_words(&[self]).unwrap()
    }
}

// SNIPPET utils
pub fn yn(result: bool) {
    if result {
        println!("Yes");
    } else {
        println!("No");
    }
}
#[allow(non_snake_case)]
pub fn YN(result: bool) {
    if result {
        println!("YES");
    } else {
        println!("NO");
    }
}
pub fn exit(msg: impl std::fmt::Display) -> ! {
    println!("{}", msg);
    std::process::exit(0)
}
#[macro_export]
#[cfg(local)]
macro_rules! dbg {
    () => {
        {
            use std::io::{self, Write};
            writeln!(io::stderr(), "{}: dbg", line!()).unwrap();
        }
    };
    ($e: expr) => {
        {
            use std::io::{self, Write};
            let result = $e;
            writeln!(io::stderr(), "{}: {} = {:?}",
                     line!(), stringify!($e), result)
                .unwrap();
            result
        }
    }
}
#[macro_export]
#[cfg(not(local))]
macro_rules! dbg {
    () => {};
    ($e: expr) => {
        { $e }
    }
}

// SNIPPET option
pub trait BoolExt {
    fn then<T>(self, value: T) -> Option<T>;
    fn then_with<T, F>(self, f: F) -> Option<T> where F: FnOnce() -> T;
    fn and<T>(self, option: Option<T>) -> Option<T>;
    fn and_then<T, F>(self, f: F) -> Option<T> where F: FnOnce() -> Option<T>;
}
impl BoolExt for bool {
    fn then<T>(self, value: T) -> Option<T> {
        if self { Some(value) } else { None }
    }
    fn then_with<T, F>(self, f: F) -> Option<T> where F: FnOnce() -> T {
        if self { Some(f()) } else { None }
    }
    fn and<T>(self, option: Option<T>) -> Option<T> {
        if self { option } else { None }
    }
    fn and_then<T, F>(self, f: F) -> Option<T> where F: FnOnce() -> Option<T> {
        if self { f() } else { None }
    }
}
pub trait OptionExt<T> {
    fn to_string_or<U: std::fmt::Display>(&self, default: U) -> String
    where
        T: std::fmt::Display;
}
impl<T> OptionExt<T> for Option<T> {
    fn to_string_or<U: std::fmt::Display>(&self, default: U) -> String
    where
        T: std::fmt::Display
    {
        self.as_ref().map(|x| x.to_string()).unwrap_or(default.to_string())
    }
    /*
    fn get_or_insert_with<F: FnOnce() -> T>(&mut self, f: F) -> &mut T {
        match *self {
            None => *self = Some(f()),
            _ => ()
        }
        self.as_mut().unwrap()
    }
    */
}
pub trait Guard: Sized {
    fn guard(self, pred: impl FnOnce(&Self) -> bool) -> Option<Self>;
}
impl<T> Guard for T {
    fn guard(self, pred: impl FnOnce(&T) -> bool) -> Option<T> {
        if pred(&self) { Some(self) } else { None }
    }
}

// SNIPPET iter
pub struct Chunks<I: Iterator> {
    iter: I,
    size: usize
}
impl<I: Iterator> Iterator for Chunks<I> {
    type Item = Vec<I::Item>;
    fn next(&mut self) -> Option<Self::Item> {
        let first = self.iter.next();
        if first.is_none() {
            return None;
        }
        let mut chunk = Vec::with_capacity(self.size);
        chunk.push(first.unwrap());
        for _ in 0..self.size-1 {
            match self.iter.next() {
                Some(x) => chunk.push(x),
                None => break
            }
        }
        Some(chunk)
    }
}
#[derive(Clone)]
pub struct LScan<I: Iterator, S: Clone, F: FnMut(&S, I::Item) -> S> {
    iter: I,
    state: Option<S>,
    f: F,
}
impl<I: Iterator, S: Clone, F> Iterator for LScan<I, S, F>
where
    F: FnMut(&S, I::Item) -> S,
{
    type Item = S;
    fn next(&mut self) -> Option<S> {
        if self.state.is_none() {
            return None;
        }
        let state_inner = self.state.take().unwrap();
        if let Some(item) = self.iter.next() {
            self.state = Some((self.f)(&state_inner, item));
        }
        Some(state_inner)
    }
}
pub struct GroupBy<K: Eq, I: Iterator, F: FnMut(&I::Item) -> K> {
    cur: Option<(I::Item, K)>,
    iter: I,
    key_fn: F
}
impl<K: Eq, I: Iterator, F: FnMut(&I::Item) -> K> Iterator for GroupBy<K, I, F> {
    type Item = (K, Vec<I::Item>);
    fn next(&mut self) -> Option<(K, Vec<I::Item>)> {
        let cur = self.cur.take();
        cur.map(|(item, key)| {
            let mut group = vec![item];
            loop {
                let next = self.iter.next();
                match next {
                    Some(next_item) => {
                        let next_key = (self.key_fn)(&next_item);
                        if key == next_key {
                            group.push(next_item);
                        } else {
                            self.cur = Some((next_item, next_key));
                            break;
                        }
                    }
                    None => {
                        self.cur = None;
                        break;
                    }
                }
            }
            (key, group)
        })
    }
}
pub struct RunLength<I: Iterator> {
    cur: Option<I::Item>,
    iter: I
}
impl<I: Iterator> Iterator for RunLength<I> where I::Item: Eq {
    type Item = (I::Item, usize);
    fn next(&mut self) -> Option<(I::Item, usize)> {
        let cur = self.cur.take();
        cur.map(|value| {
            let mut length = 1;
            loop {
                let next = self.iter.next();
                match next {
                    Some(next_value) => {
                        if value == next_value {
                            length += 1;
                        } else {
                            self.cur = Some(next_value);
                            break;
                        }
                    }
                    None => {
                        self.cur = None;
                        break;
                    }
                }
            }
            (value, length)
        })
    }
}
pub trait IteratorExt: Iterator {
    fn chunks(self, size: usize) -> Chunks<Self> where Self: Sized {
        assert!(size > 0);
        Chunks {
            iter: self,
            size: size
        }
    }
    fn lscan<S: Clone, F>(self, state: S, f: F) -> LScan<Self, S, F>
    where
        Self: Sized,
        F: FnMut(&S, Self::Item) -> S,
    {
        LScan {
            iter: self,
            state: Some(state),
            f: f,
        }
    }
    fn lscan1<F>(mut self, f: F) -> Option<LScan<Self, Self::Item, F>>
    where
        Self: Sized,
        Self::Item: Clone,
        F: FnMut(&Self::Item, Self::Item) -> Self::Item
    {
        self.next().map(|first| self.lscan(first, f))
    }
    fn get_unique(mut self) -> Option<Self::Item> where Self: Sized, Self::Item: Eq {
        let first_opt = self.next();
        first_opt.and_then(|first| {
            if self.all(|item| item == first) { Some(first) } else { None }
        })
    }
    fn group_by<K: Eq, F: FnMut(&Self::Item) -> K>(mut self, mut f: F) -> GroupBy<K, Self, F> where Self: Sized {
        let next = self.next();
        GroupBy {
            cur: next.map(|item| {
                let key = f(&item);
                (item, key)
            }),
            iter: self,
            key_fn: f
        }
    }
    fn run_length(mut self) -> RunLength<Self> where Self: Sized, Self::Item: Eq {
        RunLength {
            cur: self.next(),
            iter: self
        }
    }
    fn join<T: std::fmt::Display>(mut self, sep: T) -> String
    where
        Self: Sized, Self::Item: std::fmt::Display
    {
        let mut result = String::new();
        if let Some(first) = self.next() {
            result.push_str(&format!("{}", first));
        }
        for s in self {
            result.push_str(&format!("{}{}", sep, s));
        }
        result
    }
    fn cat(self) -> String where Self: Sized, Self::Item: std::fmt::Display { self.join("") }
}
impl<I: Iterator> IteratorExt for I {}
pub trait IteratorInnerProduct<T, Rhs=T>: ExactSizeIterator<Item=T> {
    fn inner_product<I, J>(self, other: I) -> Option<<T as std::ops::Mul<Rhs>>::Output>
    where
        Self: Sized,
        I: IntoIterator<Item=Rhs, IntoIter=J>,
        J: Iterator<Item=Rhs> + ExactSizeIterator,
        T: std::ops::Mul<Rhs>,
        <T as std::ops::Mul<Rhs>>::Output: std::iter::Sum
    {
        let iter = other.into_iter();
        (self.len() == iter.len()).then_with(|| {
            self.zip(iter).map(|(a, b)| a * b).sum()
        })
    }
}
impl<T1, T2, I> IteratorInnerProduct<T1, T2> for I
where
    I: Iterator<Item=T1> + ExactSizeIterator,
    T1: std::ops::Mul<T2>
{}
pub struct Unfold<T, F> where F: FnMut(&T) -> Option<T> {
    state: Option<T>,
    f: F
}
impl<T, F> Iterator for Unfold<T, F> where F: FnMut(&T) -> Option<T> {
    type Item = T;
    fn next(&mut self) -> Option<T> {
        if self.state.is_none() {
            return None;
        }
        let state_inner = self.state.take().unwrap();
        self.state = (self.f)(&state_inner);
        Some(state_inner)
    }
}
pub fn unfold<T, F>(init: T, f: F) -> Unfold<T, F> where F: FnMut(&T) -> Option<T> {
    Unfold { state: Some(init), f: f }
}
pub struct Iterate<T, F> where F: FnMut(&T) -> T {
    state: T,
    f: F
}
impl<T, F> Iterator for Iterate<T, F> where F: FnMut(&T) -> T {
    type Item = T;
    fn next(&mut self) -> Option<T> {
        use std::mem::swap;
        let mut state = (self.f)(&self.state);
        swap(&mut state, &mut self.state);
        Some(state)
    }
}
pub fn iterate<T, F>(init: T, f: F) -> Iterate<T, F>
where
    F: FnMut(&T) -> T,
{
    Iterate { state: init, f: f }
}

// SNIPPET num_types
pub trait WithZero: Sized +
    for<'a> std::ops::Add<&'a Self, Output=Self> +
    for<'a> std::ops::AddAssign<&'a Self>
{
    fn zero() -> Self;
}
pub trait WithOne: Sized +
    for<'a> std::ops::Mul<&'a Self, Output=Self> +
    for<'a> std::ops::MulAssign<&'a Self>
{
    fn one() -> Self;
}
pub trait Numeric: WithZero + WithOne + PartialEq<Self> +
    for<'a> std::ops::Sub<&'a Self, Output=Self> +
    for<'a> std::ops::SubAssign<&'a Self> +
    for<'a> std::ops::Div<&'a Self, Output=Self> +
    for<'a> std::ops::DivAssign<&'a Self>
{}
pub trait Integer: Numeric + Eq + Ord {}
pub trait Float: Numeric {}

// SNIPPET int
pub trait PrimitiveInteger: Integer {
    fn abs_diff(self, rhs: Self) -> Self;
    fn rem_euclid(self, rhs: Self) -> Self;
}
macro_rules! impl_primitive_integer {
    ( $($t: ty)* ) => { $(
        impl WithZero for $t {
            fn zero() -> $t { 0 }
        }
        impl WithOne for $t {
            fn one() -> $t { 1 }
        }
        impl Numeric for $t {}
        impl Integer for $t {}
        impl PrimitiveInteger for $t {
            fn abs_diff(self, rhs: $t) -> $t {
                if self < rhs { rhs - self } else { self - rhs }
            }
            #[allow(unused_comparisons)]
            fn rem_euclid(self, rhs: $t) -> $t {
                let r = self % rhs;
                if r < 0 {
                    if rhs < 0 { r - rhs } else { r + rhs }
                } else {
                    r
                }
            }
        }
    )* }
}
impl_primitive_integer!(u8 u16 u32 u64 usize i8 i16 i32 i64 isize);
pub trait PrimitiveUnsigned: PrimitiveInteger {
    fn ceil_div(self, rhs: Self) -> Self;
    fn round_div(self, rhs: Self) -> Self;
    fn log2(self) -> Option<Self>;
    fn ceil_log2(self) -> Option<Self>;
    fn sqrt(self) -> Self;
}
macro_rules! impl_primitive_unsigned {
    ( $($t: ty)* ) => { $(
        impl PrimitiveUnsigned for $t {
            fn ceil_div(self, rhs: $t) -> $t {
                (self + rhs - 1) / rhs
            }
            fn round_div(self, rhs: $t) -> $t {
                (self + rhs/2) / rhs
            }
            fn log2(mut self) -> Option<$t> {
                if self == 0 {
                    None
                } else {
                    let mut ans = 0;
                    while self > 1 {
                        ans += 1;
                        self /= 2;
                    }
                    Some(ans)
                }
            }
            fn ceil_log2(self) -> Option<$t> {
                self.log2().map(|x| {
                    (self + ((1<<x) - 1)).log2().unwrap()
                })
            }
            fn sqrt(self) -> $t {
                (self as f64).sqrt() as $t
            }
        }
    )* }
}
impl_primitive_unsigned!(u8 u16 u32 u64 usize);
pub fn gcd(a: u64, b: u64) -> u64 {
    if b == 0 { a } else { gcd(b, a%b) }
}
pub fn bezout(a: i64, b: i64) -> (i64, i64, u64) {
    let (x, y, g) = bezout_sub((a * a.signum()) as u64, (b * b.signum()) as u64);
    (x * a.signum(), y * b.signum(), g)
}
fn bezout_sub(a: u64, b: u64) -> (i64, i64, u64) {
    if b == 0 { (1, 0, a) } else {
        let m = (a / b) as i64;
        let (x, y, g) = bezout_sub(b, a%b);
        (y, x - m*y, g)
    }
}
macro_rules! impl_primitive_float {
    ( $($t:ty)* ) => { $(
        impl WithZero for $t {
            fn zero() -> $t { 0.0 }
        }
        impl WithOne for $t {
            fn one() -> $t { 1.0 }
        }
        impl Numeric for $t {}
        impl Float for $t {}
    )* }
}
impl_primitive_float!(f32 f64);

// END SNIPPETS
// Here is the documentation: https://yoshrc.github.io/rust-atcoder-snippets/atcoder_snippets/index.html

use std::collections::VecDeque;

fn bipartite(v: usize, col: usize, p: Option<usize>, adj: &[Vec<usize>], vss: &mut [Vec<usize>; 2]) {
    vss[col].push(v);
    for &next in &adj[v] {
        if Some(next) == p {
            continue;
        }
        bipartite(next, (col+1) % 2, Some(v), adj, vss);
    }
}

fn get_branch(prevs: &[Option<usize>], mut v: usize) -> Vec<usize> {
    let mut res = vec![v];
    while v != 0 {
        v = prevs[v].unwrap();
        res.push(v);
    }
    res
}

fn find_pure_cycle(adj: &[Vec<usize>]) -> Vec<usize> {
    let mut queue = VecDeque::new();
    let mut prevs = vec![None; adj.len()];
    queue.push_back(0);
    prevs[0] = Some(0);
    while let Some(v) = queue.pop_front() {
        dbg!(v);
        dbg!(&prevs);
        for &next in &adj[v] {
            dbg!(next);
            if prevs[v] == Some(next) {
                continue;
            }
            if prevs[next].is_some() {
                let mut branch1 = get_branch(&prevs, v);
                let mut branch2 = get_branch(&prevs, next);
                dbg!((&branch1, &branch2));
                let mut joint = branch1[0];
                loop {
                    match (branch1.last(), branch2.last()) {
                        (Some(&x), Some(&y)) if x == y => {
                            branch1.pop();
                            branch2.pop();
                            joint = x;
                        }
                        _ => break
                    }
                }
                branch1.push(joint);
                branch2.reverse();
                branch1.extend(branch2);
                return branch1;
            }

            prevs[next] = Some(v);
            queue.push_back(next);
        }
    }
    unreachable!()
}

fn main() {
    read!(n = usize, m = usize, k = usize);
    let mut adj = vec![Vec::new(); n];
    for (u, v) in read_lines::<(usize_, usize_)>() {
        adj[u].push(v);
        adj[v].push(u);
    }
    if m == n-1 {
        let mut vss = [Vec::new(), Vec::new()];
        bipartite(0, 0, None, &adj, &mut vss);
        println!("1");
        let count = k.ceil_div(2);
        if vss[0].len() >= count {
            println!("{}", vss[0].iter().map(|&v| v+1).take(count).join(' '));
        } else {
            println!("{}", vss[1].iter().map(|&v| v+1).take(count).join(' '));
        }
    } else {
        let cycle = find_pure_cycle(&adj);
        if cycle.len() <= k {
            println!("2");
            println!("{}", cycle.len());
            println!("{}", cycle.into_iter().map(|v| v+1).join(' '));
        } else {
            let count = k.ceil_div(2);
            println!("1");
            println!("{}", cycle.into_iter().map(|v| v+1).step_by(2).take(count).join(' '));
        }
    }
}


// ---------- begin Lowest Common Ancestor ----------
struct LCA {
    graph: Vec<Vec<usize>>,
    path_root: Vec<usize>,
    path_parent: Vec<usize>,
    index: Vec<usize>
}

impl LCA {
    fn new(n: usize) -> Self {
        LCA {
            graph: vec![vec![]; n],
            path_root: vec![],
            path_parent: vec![],
            index: vec![],
        }
    }
    fn add_edge(&mut self, a: usize, b: usize) {
        self.graph[a].push(b);
        self.graph[b].push(a);
    }
    fn build(&mut self, root: usize) {
        let mut q = vec![];
        let mut stack = vec![(root, root)];
        let graph = &mut self.graph;
        while let Some((v, p)) = stack.pop() {
            q.push(v);
            if let Some(k) = graph[v].iter().position(|u| *u == p) {
                graph[v].swap_remove(k);
            }
            for &u in graph[v].iter() {
                stack.push((u, v));
            }
        }
        let n = graph.len();
        let mut size = vec![1; n];
        for &v in q.iter().rev() {
            if graph[v].is_empty() {
                continue;
            }
            let mut max = (0, 0);
            for (i, &u) in graph[v].iter().enumerate() {
                size[v] += size[u];
                if size[u] > max.0 {
                    max = (size[u], i);
                }
            }
            graph[v].swap(0, max.1);
        }
        let mut path_root = vec![root; n];
        let mut path_parent = vec![root; n];
        let mut index = vec![n; n];
        let mut stack = vec![root];
        let mut k = 0;
        while let Some(v) = stack.pop() {
            index[v] = k;
            k += 1;
            if graph[v].is_empty() {
                continue;
            }
            for &u in graph[v].iter().skip(1) {
                path_root[u] = u;
                path_parent[u] = v;
                stack.push(u);
            }
            let u = graph[v][0];
            path_root[u] = path_root[v];
            path_parent[u] = path_parent[v];
            stack.push(u);
        }
        self.path_root = path_root;
        self.path_parent = path_parent;
        self.index = index;
    }
    fn query(&self, mut a: usize, mut b: usize) -> usize {
        let path = &self.path_root;
        let parent = &self.path_parent;
        let index = &self.index;
        while path[a] != path[b] {
            if index[a] < index[b] {
                b = parent[b];
            } else {
                a = parent[a];
            }
        }
        if index[a] < index[b] {a} else {b}
    }
}
// ---------- end Lowest Common Ancestor ----------
//https://qiita.com/tanakh/items/0ba42c7ca36cd29d0ac8 より
macro_rules! input {
    (source = $s:expr, $($r:tt)*) => {
        let mut iter = $s.split_whitespace();
        input_inner!{iter, $($r)*}
    };
    ($($r:tt)*) => {
        let s = {
            use std::io::Read;
            let mut s = String::new();
            std::io::stdin().read_to_string(&mut s).unwrap();
            s
        };
        let mut iter = s.split_whitespace();
        input_inner!{iter, $($r)*}
    };
}

macro_rules! input_inner {
    ($iter:expr) => {};
    ($iter:expr, ) => {};
    ($iter:expr, $var:ident : $t:tt $($r:tt)*) => {
        let $var = read_value!($iter, $t);
        input_inner!{$iter $($r)*}
    };
}

macro_rules! read_value {
    ($iter:expr, ( $($t:tt),* )) => {
        ( $(read_value!($iter, $t)),* )
    };
    ($iter:expr, [ $t:tt ; $len:expr ]) => {
        (0..$len).map(|_| read_value!($iter, $t)).collect::<Vec<_>>()
    };
    ($iter:expr, chars) => {
        read_value!($iter, String).chars().collect::<Vec<char>>()
    };
    ($iter:expr, bytes) => {
        read_value!($iter, String).bytes().collect::<Vec<u8>>()
    };
    ($iter:expr, usize1) => {
        read_value!($iter, usize) - 1
    };
    ($iter:expr, $t:ty) => {
        $iter.next().unwrap().parse::<$t>().expect("Parse error")
    };
}

//

fn run() {
    input! {
        n: usize,
        m: usize,
        k: usize,
        e: [(usize1, usize1); m],
    }
    let mut g = vec![vec![]; n];
    for &(a, b) in e.iter() {
        g[a].push(b);
        g[b].push(a);
    }
    let root = 0;
    let mut used = vec![false; n];
    let mut parent = vec![root; n];
    let mut stack = vec![root];
    used[root] = true;
    let mut lca = LCA::new(n);
    let mut back = vec![];
    let mut q = vec![];
    while let Some(v) = stack.pop() {
        q.push(v);
        for &u in g[v].iter() {
            if u == parent[v] {
                continue;
            }
            if !used[u] {
                used[u] = true;
                parent[u] = v;
                lca.add_edge(u, v);
                stack.push(u);
            } else {
                back.push((u, v));
            }
        }
    }
    lca.build(root);
    if let Some((u, v)) = back.pop() {
        let p = lca.query(u, v);
        let mut ans = std::collections::VecDeque::new();
        let mut x = u;
        while x != p {
            ans.push_front(x);
            x = parent[x];
        }
        ans.push_front(x);
        let mut x = v;
        while x != p {
            ans.push_back(x);
            x = parent[x];
        }
        let mut s = String::new();
        if ans.len() > k {
            s.push_str(&format!("1\n"));
            for a in ans.iter().step_by(2).take((k + 1) / 2) {
                s.push_str(&format!("{} ", *a + 1));
            }
        } else {
            s.push_str(&format!("2\n{}\n", ans.len()));
            for a in ans {
                s.push_str(&format!("{} ", a + 1));
            }
        }
        s.pop();
        println!("{}", s);
    } else {
        let mut s = String::new();
        s.push_str(&format!("1\n"));
        let mut used = vec![false; n];
        let mut cnt = (k + 1) / 2;
        for &v in q.iter().rev() {
            if !used[v] && cnt > 0 {
                used[v] = true;
                used[parent[v]] = true;
                cnt -= 1;
                s.push_str(&format!("{} ", v + 1));
            }
        }
        s.pop();
        println!("{}", s);
    }
}

fn main() {
    run();
}


#![allow(unused_imports)]
#![allow(non_snake_case)]
use std::cmp::*;
use std::collections::*;
use std::io::Write;

#[allow(unused_macros)]
macro_rules! debug {
    ($($e:expr),*) => {
        #[cfg(debug_assertions)]
        $({
            let (e, mut err) = (stringify!($e), std::io::stderr());
            writeln!(err, "{} = {:?}", e, $e).unwrap()
        })*
    };
}

fn main() {
    let v = read_vec::<usize>();
    let (n, m, k) = (v[0], v[1], v[2]);

    let mut g = vec![vec![]; n];
    let mut edges = vec![];
    for _ in 0..m {
        let v = read_vec::<usize>();
        let (a, b) = (v[0] - 1, v[1] - 1);
        g[a].push(b);
        g[b].push(a);
        edges.push((a, b));
    }

    // tree
    if m == n - 1 {
        let color = is_biparate(&g).unwrap();
        let ps = (0..n).filter(|&x| color[x] == 1).collect::<Vec<_>>();
        let ns = (0..n).filter(|&x| color[x] == -1).collect::<Vec<_>>();

        println!("1");
        if ps.len() >= ns.len() {
            for ans in ps.into_iter().take((k + 1) / 2) {
                print!("{} ", ans + 1);
            }
        } else {
            for ans in ns.into_iter().take((k + 1) / 2) {
                print!("{} ", ans + 1);
            }
        }
        println!("");
        return;
    }

    let mut tree = vec![vec![]; n];
    let mut backs = vec![];
    let root = 0;
    let mut used = vec![false; n];
    let mut parent = vec![root; n];
    let mut stack = vec![root];
    while let Some(v) = stack.pop() {
        for &u in g[v].iter() {
            if u == parent[v] {
                continue;
            }
            if !used[u] {
                used[u] = true;
                parent[u] = v;
                tree[u].push(v);
                tree[v].push(u);
                stack.push(u);
            } else {
                backs.push((u, v));
            }
        }
    }

    let lca = LCA::new(&tree);
    let (u, v) = backs[0];

    let p = lca.lca(u, v);

    let mut answers1 = vec![u];
    let mut cur = u;
    while cur != p {
        cur = lca.parent[0][cur].unwrap();
        answers1.push(cur);
    }
    let mut cur = v;
    let mut answers2 = vec![v];
    while cur != p {
        cur = lca.parent[0][cur].unwrap();
        answers2.push(cur);
    }
    answers2.pop();
    answers2.reverse();
    answers1.append(&mut answers2);

    if answers1.len() <= k {
        println!("2");
        println!("{}", answers1.len());
        for ans in answers1 {
            print!("{} ", ans + 1);
        }
        println!("");
    } else {
        println!("1");
        let answers = (0..answers1.len())
            .filter(|&x| x % 2 == 0)
            .take((k + 1) / 2)
            .map(|x| answers1[x]);
        for ans in answers {
            print!("{} ", ans + 1);
        }
        println!("");
    }
}

fn read<T: std::str::FromStr>() -> T {
    let mut s = String::new();
    std::io::stdin().read_line(&mut s).ok();
    s.trim().parse().ok().unwrap()
}

fn read_vec<T: std::str::FromStr>() -> Vec<T> {
    read::<String>()
        .split_whitespace()
        .map(|e| e.parse().ok().unwrap())
        .collect()
}

fn dfs(start: usize, graph: &Vec<Vec<usize>>, cur_color: i32, color: &mut Vec<i32>) -> bool {
    if color[start] != 0 {
        return cur_color == color[start];
    }
    color[start] = cur_color;
    for &to in graph[start].iter() {
        if !dfs(to, &graph, -1 * cur_color, color) {
            return false;
        }
    }
    true
}

fn is_biparate(graph: &Vec<Vec<usize>>) -> Option<Vec<i32>> {
    let mut color = vec![0; graph.len()];
    for i in 0..graph.len() {
        if color[i] == 0 {
            if !dfs(i, graph, 1, &mut color) {
                return None;
            }
        }
    }
    Some(color)
}

struct LCA {
    depth: Vec<usize>,
    parent: Vec<Vec<Option<usize>>>,
    log_v: usize,
}

impl LCA {
    fn new(g: &Vec<Vec<usize>>) -> LCA {
        LCA::with_root(g, 0)
    }

    fn with_root(g: &Vec<Vec<usize>>, root: usize) -> LCA {
        let n = g.len();
        let log_v = (1..).find(|i| 1usize << i > n).unwrap();
        let mut lca = LCA {
            depth: vec![0usize; n],
            parent: vec![vec![None; n]; 19], // support 2 ^ 20
            log_v: log_v,
        };
        lca.dfs(root, None, 0, &g);
        lca.doubling(g.len());
        lca
    }

    fn doubling(&mut self, n: usize) {
        for k in 0..self.log_v {
            for v in 0..n {
                if let Some(parent_v) = self.parent[k][v] {
                    self.parent[k + 1][v] = self.parent[k][parent_v];
                }
            }
        }
    }

    fn dfs(&mut self, cur: usize, p: Option<usize>, cur_depth: usize, g: &Vec<Vec<usize>>) {
        self.parent[0][cur] = p;
        self.depth[cur] = cur_depth;
        for &to in g[cur].iter() {
            if Some(to) == p {
                continue;
            }
            self.dfs(to, Some(cur), cur_depth + 1, g);
        }
    }

    fn lca(&self, mut u: usize, mut v: usize) -> usize {
        if self.depth[u] > self.depth[v] {
            std::mem::swap(&mut u, &mut v);
        }
        while self.depth[v] != self.depth[u] {
            v = self.parent[(self.depth[v] - self.depth[u]).trailing_zeros() as usize][v].unwrap();
        }
        if u == v {
            return u;
        }
        for k in (0..self.parent.len()).rev() {
            if self.parent[k][u] != self.parent[k][v] {
                u = self.parent[k][u].unwrap();
                v = self.parent[k][v].unwrap();
            }
        }
        self.parent[0][u].unwrap()
    }
}


#![allow(unused_imports)]
#![allow(non_snake_case)]
use std::cmp::*;
use std::collections::*;
use std::io::Write;

#[allow(unused_macros)]
macro_rules! debug {
    ($($e:expr),*) => {
        #[cfg(debug_assertions)]
        $({
            let (e, mut err) = (stringify!($e), std::io::stderr());
            writeln!(err, "{} = {:?}", e, $e).unwrap()
        })*
    };
}

fn main() {
    let v = read_vec::<usize>();
    let (n, m, k) = (v[0], v[1], v[2]);

    let mut g = vec![vec![]; n];
    let mut edges = vec![];
    for i in 0..m {
        let v = read_vec::<usize>();
        let (a, b) = (v[0] - 1, v[1] - 1);
        g[a].push(b);
        g[b].push(a);
        edges.push((a, b));
    }

    // tree
    if let Some(color) = is_biparate(&g) {
        let ps = (0..n).filter(|&x| color[x] == 1).collect::<Vec<_>>();
        let ns = (0..n).filter(|&x| color[x] == -1).collect::<Vec<_>>();

        println!("1");
        if ps.len() >= ns.len() {
            for ans in ps.into_iter().take((k + 1) / 2) {
                print!("{} ", ans + 1);
            }
        } else {
            for ans in ns.into_iter().take((k + 1) / 2) {
                print!("{} ", ans + 1);
            }
        }
        println!("");
        return;
    }

    let mut tree = vec![vec![]; n];
    let mut backs = vec![];
    let root = 0;
    let mut used = vec![false; n];
    let mut parent = vec![root; n];
    let mut stack = vec![root];
    while let Some(v) = stack.pop() {
        for &u in g[v].iter() {
            if u == parent[v] {
                continue;
            }
            if !used[u] {
                used[u] = true;
                parent[u] = v;
                tree[u].push(v);
                tree[v].push(u);
                stack.push(u);
            } else {
                backs.push((u, v));
            }
        }
    }

    let lca = LCA::new(&tree);
    let (u, v) = backs[0];

    let p = lca.lca(u, v);

    let mut answers1 = vec![u];
    let mut cur = u;
    debug!(u, v, p);
    while cur != p {
        cur = lca.parent[0][cur].unwrap();
        answers1.push(cur);
    }
    let mut cur = v;
    let mut answers2 = vec![v];
    while cur != p {
        cur = lca.parent[0][cur].unwrap();
        answers2.push(cur);
    }
    answers2.pop();
    answers2.reverse();
    debug!(answers1, answers2);
    answers1.append(&mut answers2);
    debug!(answers1);

    if answers1.len() <= k {
        println!("2");
        println!("{}", answers1.len());
        for ans in answers1 {
            print!("{} ", ans + 1);
        }
        println!("");
    } else {
        println!("1");
        let answers = (0..answers1.len())
            .filter(|&x| x % 2 == 0)
            .take((k + 1) / 2)
            .map(|x| answers1[x]);
        for ans in answers {
            print!("{} ", ans + 1);
        }
        println!("");
    }
}

fn read<T: std::str::FromStr>() -> T {
    let mut s = String::new();
    std::io::stdin().read_line(&mut s).ok();
    s.trim().parse().ok().unwrap()
}

fn read_vec<T: std::str::FromStr>() -> Vec<T> {
    read::<String>()
        .split_whitespace()
        .map(|e| e.parse().ok().unwrap())
        .collect()
}

fn dfs(start: usize, graph: &Vec<Vec<usize>>, cur_color: i32, color: &mut Vec<i32>) -> bool {
    if color[start] != 0 {
        return cur_color == color[start];
    }
    color[start] = cur_color;
    for &to in graph[start].iter() {
        if !dfs(to, &graph, -1 * cur_color, color) {
            return false;
        }
    }
    true
}

fn is_biparate(graph: &Vec<Vec<usize>>) -> Option<Vec<i32>> {
    let mut color = vec![0; graph.len()];
    for i in 0..graph.len() {
        if color[i] == 0 {
            if !dfs(i, graph, 1, &mut color) {
                return None;
            }
        }
    }
    Some(color)
}

struct LCA {
    depth: Vec<usize>,
    parent: Vec<Vec<Option<usize>>>,
    log_v: usize,
}

impl LCA {
    fn new(g: &Vec<Vec<usize>>) -> LCA {
        LCA::with_root(g, 0)
    }

    fn with_root(g: &Vec<Vec<usize>>, root: usize) -> LCA {
        let n = g.len();
        let log_v = (1..).find(|i| 1usize << i > n).unwrap();
        let mut lca = LCA {
            depth: vec![0usize; n],
            parent: vec![vec![None; n]; 20 + 1], // support 2 ^ 20
            log_v: log_v,
        };
        lca.dfs(root, None, 0, &g);
        lca.doubling(g.len());
        lca
    }

    fn doubling(&mut self, n: usize) {
        for k in 0..self.log_v {
            for v in 0..n {
                if let Some(parent_v) = self.parent[k][v] {
                    self.parent[k + 1][v] = self.parent[k][parent_v];
                }
            }
        }
    }

    fn dfs(&mut self, cur: usize, p: Option<usize>, cur_depth: usize, g: &Vec<Vec<usize>>) {
        self.parent[0][cur] = p;
        self.depth[cur] = cur_depth;
        for &to in g[cur].iter() {
            if Some(to) == p {
                continue;
            }
            self.dfs(to, Some(cur), cur_depth + 1, g);
        }
    }

    fn lca(&self, mut u: usize, mut v: usize) -> usize {
        if self.depth[u] > self.depth[v] {
            std::mem::swap(&mut u, &mut v);
        }
        while self.depth[v] != self.depth[u] {
            v = self.parent[(self.depth[v] - self.depth[u]).trailing_zeros() as usize][v].unwrap();
        }
        if u == v {
            return u;
        }
        for k in (0..self.parent.len()).rev() {
            if self.parent[k][u] != self.parent[k][v] {
                u = self.parent[k][u].unwrap();
                v = self.parent[k][v].unwrap();
            }
        }
        self.parent[0][u].unwrap()
    }
}


#![allow(unused_imports)]
#![allow(non_snake_case)]
use std::cmp::*;
use std::collections::*;
use std::io::Write;

#[allow(unused_macros)]
macro_rules! debug {
    ($($e:expr),*) => {
        #[cfg(debug_assertions)]
        $({
            let (e, mut err) = (stringify!($e), std::io::stderr());
            writeln!(err, "{} = {:?}", e, $e).unwrap()
        })*
    };
}

fn main() {
    let v = read_vec::<usize>();
    let (n, m, k) = (v[0], v[1], v[2]);

    let mut g = vec![vec![]; n];
    let mut edges = vec![];
    for _ in 0..m {
        let v = read_vec::<usize>();
        let (a, b) = (v[0] - 1, v[1] - 1);
        g[a].push(b);
        g[b].push(a);
        edges.push((a, b));
    }

    // tree
    if m == n - 1 {
        let color = is_biparate(&g).unwrap();
        let ps = (0..n).filter(|&x| color[x] == 1).collect::<Vec<_>>();
        let ns = (0..n).filter(|&x| color[x] == -1).collect::<Vec<_>>();

        println!("1");
        if ps.len() >= ns.len() {
            for ans in ps.into_iter().take((k + 1) / 2) {
                print!("{} ", ans + 1);
            }
        } else {
            for ans in ns.into_iter().take((k + 1) / 2) {
                print!("{} ", ans + 1);
            }
        }
        println!("");
        return;
    }

    let mut tree = vec![vec![]; n];
    let mut backs = vec![];
    let root = 0;
    let mut used = vec![false; n];
    let mut parent = vec![root; n];
    let mut stack = VecDeque::new();
    stack.push_back(root);
    while let Some(v) = stack.pop_front() {
        for &u in g[v].iter() {
            if u == parent[v] {
                continue;
            }
            if !used[u] {
                used[u] = true;
                parent[u] = v;
                tree[u].push(v);
                tree[v].push(u);
                stack.push_back(u);
            } else {
                backs.push((u, v));
            }
        }
    }
    debug!(tree);
    debug!(backs);

    let lca = LCA::new(&tree);
    let (u, v) = backs[backs.len() - 1];

    let p = lca.lca(u, v);

    let mut answers1 = vec![u];
    let mut cur = u;
    while cur != p {
        cur = lca.parent[0][cur].unwrap();
        answers1.push(cur);
    }
    let mut cur = v;
    let mut answers2 = vec![v];
    while cur != p {
        cur = lca.parent[0][cur].unwrap();
        answers2.push(cur);
    }
    answers2.pop();
    answers2.reverse();
    answers1.append(&mut answers2);

    if answers1.len() <= k {
        println!("2");
        println!("{}", answers1.len());
        for ans in answers1 {
            print!("{} ", ans + 1);
        }
        println!("");
    } else {
        println!("1");
        let answers = (0..answers1.len())
            .filter(|&x| x % 2 == 0)
            .take((k + 1) / 2)
            .map(|x| answers1[x]);
        for ans in answers {
            print!("{} ", ans + 1);
        }
        println!("");
    }
}

fn read<T: std::str::FromStr>() -> T {
    let mut s = String::new();
    std::io::stdin().read_line(&mut s).ok();
    s.trim().parse().ok().unwrap()
}

fn read_vec<T: std::str::FromStr>() -> Vec<T> {
    read::<String>()
        .split_whitespace()
        .map(|e| e.parse().ok().unwrap())
        .collect()
}

fn dfs(start: usize, graph: &Vec<Vec<usize>>, cur_color: i32, color: &mut Vec<i32>) -> bool {
    if color[start] != 0 {
        return cur_color == color[start];
    }
    color[start] = cur_color;
    for &to in graph[start].iter() {
        if !dfs(to, &graph, -1 * cur_color, color) {
            return false;
        }
    }
    true
}

fn is_biparate(graph: &Vec<Vec<usize>>) -> Option<Vec<i32>> {
    let mut color = vec![0; graph.len()];
    for i in 0..graph.len() {
        if color[i] == 0 {
            if !dfs(i, graph, 1, &mut color) {
                return None;
            }
        }
    }
    Some(color)
}

struct LCA {
    depth: Vec<usize>,
    parent: Vec<Vec<Option<usize>>>,
    log_v: usize,
}

impl LCA {
    fn new(g: &Vec<Vec<usize>>) -> LCA {
        LCA::with_root(g, 0)
    }

    fn with_root(g: &Vec<Vec<usize>>, root: usize) -> LCA {
        let n = g.len();
        let log_v = (1..).find(|i| 1usize << i > n).unwrap();
        let mut lca = LCA {
            depth: vec![0usize; n],
            parent: vec![vec![None; n]; 19], // support 2 ^ 20
            log_v: log_v,
        };
        lca.dfs(root, None, 0, &g);
        lca.doubling(g.len());
        lca
    }

    fn doubling(&mut self, n: usize) {
        for k in 0..self.log_v {
            for v in 0..n {
                if let Some(parent_v) = self.parent[k][v] {
                    self.parent[k + 1][v] = self.parent[k][parent_v];
                }
            }
        }
    }

    fn dfs(&mut self, cur: usize, p: Option<usize>, cur_depth: usize, g: &Vec<Vec<usize>>) {
        self.parent[0][cur] = p;
        self.depth[cur] = cur_depth;
        for &to in g[cur].iter() {
            if Some(to) == p {
                continue;
            }
            self.dfs(to, Some(cur), cur_depth + 1, g);
        }
    }

    fn lca(&self, mut u: usize, mut v: usize) -> usize {
        if self.depth[u] > self.depth[v] {
            std::mem::swap(&mut u, &mut v);
        }
        while self.depth[v] != self.depth[u] {
            v = self.parent[(self.depth[v] - self.depth[u]).trailing_zeros() as usize][v].unwrap();
        }
        if u == v {
            return u;
        }
        for k in (0..self.parent.len()).rev() {
            if self.parent[k][u] != self.parent[k][v] {
                u = self.parent[k][u].unwrap();
                v = self.parent[k][v].unwrap();
            }
        }
        self.parent[0][u].unwrap()
    }
}


package main

import (
	"bufio"
	"bytes"
	"fmt"
	"os"
	"strconv"
)

func readInt(bytes []byte, from int, val *int) int {
	i := from
	sign := 1
	if bytes[i] == '-' {
		sign = -1
		i++
	}
	tmp := 0
	for i < len(bytes) && bytes[i] >= '0' && bytes[i] <= '9' {
		tmp = tmp*10 + int(bytes[i]-'0')
		i++
	}
	*val = tmp * sign
	return i
}

func readNum(reader *bufio.Reader) (a int) {
	bs, _ := reader.ReadBytes('\n')
	readInt(bs, 0, &a)
	return
}

func readTwoNums(reader *bufio.Reader) (a int, b int) {
	res := readNNums(reader, 2)
	a, b = res[0], res[1]
	return
}

func readThreeNums(reader *bufio.Reader) (a int, b int, c int) {
	res := readNNums(reader, 3)
	a, b, c = res[0], res[1], res[2]
	return
}

func readNNums(reader *bufio.Reader, n int) []int {
	res := make([]int, n)
	x := 0
	bs, _ := reader.ReadBytes('\n')
	for i := 0; i < n; i++ {
		for x < len(bs) && (bs[x] < '0' || bs[x] > '9') && bs[x] != '-' {
			x++
		}
		x = readInt(bs, x, &res[i])
	}
	return res
}

func readUint64(bytes []byte, from int, val *uint64) int {
	i := from

	var tmp uint64
	for i < len(bytes) && bytes[i] >= '0' && bytes[i] <= '9' {
		tmp = tmp*10 + uint64(bytes[i]-'0')
		i++
	}
	*val = tmp

	return i
}

func main() {
	reader := bufio.NewReader(os.Stdin)

	firstLine := readNNums(reader, 3)
	n, m, k := firstLine[0], firstLine[1], firstLine[2]
	E := make([][]int, m)
	for i := 0; i < m; i++ {
		E[i] = readNNums(reader, 2)
	}
	t, res := solve(n, k, E)

	fmt.Println(t)

	if t == 2 {
		fmt.Println(len(res))
	}

	var buf bytes.Buffer

	for i := 0; i < len(res); i++ {
		buf.WriteString(strconv.Itoa(res[i]))
		buf.WriteByte(' ')
	}

	fmt.Println(buf.String())
}

func solve(n, k int, E [][]int) (int, []int) {
	G := getGraph(n, E)
	pos := make([]int, n)
	for i := 0; i < n; i++ {
		pos[i] = -1
	}
	cnt := make([]int, 2)
	vis := make([]int, 0, n)
	cyc := make([]int, 0, n)
	var dfs func(u int) bool

	dfs = func(u int) bool {
		pos[u] = len(vis)
		cnt[pos[u]&1]++
		vis = append(vis, u)
		var low = -1
		for _, v := range G[u] {
			// visited before and not parent
			if pos[v] >= 0 && pos[u] > pos[v]+1 {
				low = max(low, pos[v])
			}
		}

		if low >= 0 {
			for i := low; i <= pos[u]; i++ {
				cyc = append(cyc, vis[i])
			}
			return true
		}

		for _, v := range G[u] {
			if pos[v] < 0 {
				if dfs(v) {
					return true
				}
			}
		}
		vis = vis[:pos[u]]
		return false
	}

	hasCycle := dfs(0)

	res := make([]int, 0, n)

	if !hasCycle {
		var x int
		if cnt[1] > cnt[0] {
			x = 1
		}
		for i := 0; i < n; i++ {
			if pos[i]&1 == x {
				res = append(res, i+1)
			}
		}
		return 1, res
	}

	if len(cyc) <= k {
		for i := 0; i < len(cyc); i++ {
			res = append(res, cyc[i]+1)
		}
		return 2, res
	}

	h := (k + 1) / 2

	for i := 0; i < h; i++ {
		res = append(res, cyc[2*i]+1)
	}

	return 1, res
}

func max(a, b int) int {
	if a >= b {
		return a
	}
	return b
}

func getGraph(n int, E [][]int) [][]int {
	degree := make([]int, n)
	for _, e := range E {
		u, v := e[0]-1, e[1]-1
		degree[u]++
		degree[v]++
	}
	adj := make([][]int, n)
	for i := 0; i < n; i++ {
		adj[i] = make([]int, 0, degree[i])
	}

	for _, e := range E {
		u, v := e[0]-1, e[1]-1
		adj[u] = append(adj[u], v)
		adj[v] = append(adj[v], u)
	}

	return adj
}


package main

import (
	"bufio"
	"bytes"
	"fmt"
	"os"
	"strconv"
)

func readInt(bytes []byte, from int, val *int) int {
	i := from
	sign := 1
	if bytes[i] == '-' {
		sign = -1
		i++
	}
	tmp := 0
	for i < len(bytes) && bytes[i] >= '0' && bytes[i] <= '9' {
		tmp = tmp*10 + int(bytes[i]-'0')
		i++
	}
	*val = tmp * sign
	return i
}

func readNum(reader *bufio.Reader) (a int) {
	bs, _ := reader.ReadBytes('\n')
	readInt(bs, 0, &a)
	return
}

func readTwoNums(reader *bufio.Reader) (a int, b int) {
	res := readNNums(reader, 2)
	a, b = res[0], res[1]
	return
}

func readThreeNums(reader *bufio.Reader) (a int, b int, c int) {
	res := readNNums(reader, 3)
	a, b, c = res[0], res[1], res[2]
	return
}

func readNNums(reader *bufio.Reader, n int) []int {
	res := make([]int, n)
	x := 0
	bs, _ := reader.ReadBytes('\n')
	for i := 0; i < n; i++ {
		for x < len(bs) && (bs[x] < '0' || bs[x] > '9') && bs[x] != '-' {
			x++
		}
		x = readInt(bs, x, &res[i])
	}
	return res
}

func readUint64(bytes []byte, from int, val *uint64) int {
	i := from

	var tmp uint64
	for i < len(bytes) && bytes[i] >= '0' && bytes[i] <= '9' {
		tmp = tmp*10 + uint64(bytes[i]-'0')
		i++
	}
	*val = tmp

	return i
}

func main() {
	reader := bufio.NewReader(os.Stdin)

	firstLine := readNNums(reader, 3)
	n, m, k := firstLine[0], firstLine[1], firstLine[2]
	E := make([][]int, m)
	for i := 0; i < m; i++ {
		E[i] = readNNums(reader, 2)
	}
	t, res := solve(n, k, E)

	fmt.Println(t)

	if t == 2 {
		fmt.Println(len(res))
	}

	var buf bytes.Buffer

	for i := 0; i < len(res); i++ {
		buf.WriteString(strconv.Itoa(res[i]))
		if i < len(res)-1 {
			buf.WriteByte(' ')
		}
	}
	fmt.Println(buf.String())
}

func solve(n, k int, E [][]int) (int, []int) {
	G := getGraph(n, E)
	pos := make([]int, n)
	for i := 0; i < n; i++ {
		pos[i] = -1
	}
	cnt := make([]int, 2)
	vis := make([]int, 0, n)
	cyc := make([]int, 0, n)
	var dfs func(u int) bool

	dfs = func(u int) bool {
		pos[u] = len(vis)
		cnt[pos[u]&1]++
		vis = append(vis, u)
		var low = -1
		for _, v := range G[u] {
			// visited before and not parent
			if pos[v] >= 0 && pos[u] > pos[v]+1 {
				low = max(low, pos[v])
			}
		}

		if low >= 0 {
			for i := low; i <= pos[u]; i++ {
				cyc = append(cyc, vis[i])
			}
			return true
		}

		for _, v := range G[u] {
			if pos[v] < 0 {
				if dfs(v) {
					return true
				}
			}
		}
		vis = vis[:pos[u]]
		return false
	}

	hasCycle := dfs(0)

	res := make([]int, 0, n)

	if !hasCycle {
		var x int
		if cnt[1] > cnt[0] {
			x = 1
		}
		for i := 0; i < n; i++ {
			if pos[i]&1 == x {
				res = append(res, i+1)
			}
		}
		return 1, res
	}

	if len(cyc) <= k {
		for i := 0; i < len(cyc); i++ {
			res = append(res, cyc[i]+1)
		}
		return 2, res
	}

	h := (k + 1) / 2

	for i := 0; i < h; i++ {
		res = append(res, cyc[2*i]+1)
	}

	return 1, res
}

func max(a, b int) int {
	if a >= b {
		return a
	}
	return b
}

func getGraph(n int, E [][]int) [][]int {
	degree := make([]int, n)
	for _, e := range E {
		u, v := e[0]-1, e[1]-1
		degree[u]++
		degree[v]++
	}
	adj := make([][]int, n)
	for i := 0; i < n; i++ {
		adj[i] = make([]int, 0, degree[i])
	}

	for _, e := range E {
		u, v := e[0]-1, e[1]-1
		adj[u] = append(adj[u], v)
		adj[v] = append(adj[v], u)
	}

	return adj
}


package main

import (
	"bufio"
	"bytes"
	"fmt"
	"os"
	"strconv"
)

func readInt(bytes []byte, from int, val *int) int {
	i := from
	sign := 1
	if bytes[i] == '-' {
		sign = -1
		i++
	}
	tmp := 0
	for i < len(bytes) && bytes[i] >= '0' && bytes[i] <= '9' {
		tmp = tmp*10 + int(bytes[i]-'0')
		i++
	}
	*val = tmp * sign
	return i
}

func readNum(reader *bufio.Reader) (a int) {
	bs, _ := reader.ReadBytes('\n')
	readInt(bs, 0, &a)
	return
}

func readTwoNums(reader *bufio.Reader) (a int, b int) {
	res := readNNums(reader, 2)
	a, b = res[0], res[1]
	return
}

func readThreeNums(reader *bufio.Reader) (a int, b int, c int) {
	res := readNNums(reader, 3)
	a, b, c = res[0], res[1], res[2]
	return
}

func readNNums(reader *bufio.Reader, n int) []int {
	res := make([]int, n)
	x := 0
	bs, _ := reader.ReadBytes('\n')
	for i := 0; i < n; i++ {
		for x < len(bs) && (bs[x] < '0' || bs[x] > '9') && bs[x] != '-' {
			x++
		}
		x = readInt(bs, x, &res[i])
	}
	return res
}

func readUint64(bytes []byte, from int, val *uint64) int {
	i := from

	var tmp uint64
	for i < len(bytes) && bytes[i] >= '0' && bytes[i] <= '9' {
		tmp = tmp*10 + uint64(bytes[i]-'0')
		i++
	}
	*val = tmp

	return i
}

func main() {
	reader := bufio.NewReader(os.Stdin)

	firstLine := readNNums(reader, 3)
	n, m, k := firstLine[0], firstLine[1], firstLine[2]
	E := make([][]int, m)
	for i := 0; i < m; i++ {
		E[i] = readNNums(reader, 2)
	}
	t, res := solve(n, k, E)

	fmt.Println(t)

	if t == 2 {
		fmt.Println(len(res))
	}

	var buf bytes.Buffer

	for i := 0; i < len(res); i++ {
		buf.WriteString(strconv.Itoa(res[i]))
		buf.WriteByte(' ')
	}

	fmt.Println(buf.String())
}

func solve(n, k int, E [][]int) (int, []int) {
	G := getGraph(n, E)
	pos := make([]int, n)
	for i := 0; i < n; i++ {
		pos[i] = -1
	}
	cnt := make([]int, 2)
	vis := make([]int, 0, n)
	cyc := make([]int, 0, n)
	var dfs func(u int) bool

	dfs = func(u int) bool {
		pos[u] = len(vis)
		cnt[pos[u]&1]++
		vis = append(vis, u)
		var low = -1
		for _, v := range G[u] {
			// visited before and not parent
			if pos[v] >= 0 && pos[u] > pos[v]+1 {
				low = max(low, pos[v])
			}
		}

		if low >= 0 {
			for i := low; i <= pos[u]; i++ {
				cyc = append(cyc, vis[i])
			}
			return true
		}

		for _, v := range G[u] {
			if pos[v] < 0 {
				if dfs(v) {
					return true
				}
			}
		}

		return false
	}

	hasCycle := dfs(0)

	res := make([]int, 0, n)

	if !hasCycle {
		var x int
		if cnt[1] > cnt[0] {
			x = 1
		}
		for i := 0; i < n; i++ {
			if pos[i]&1 == x {
				res = append(res, i+1)
			}
		}
		return 1, res
	}

	if len(cyc) <= k {
		for i := 0; i < len(cyc); i++ {
			res = append(res, cyc[i]+1)
		}
		return 2, res
	}

	h := (k + 1) / 2

	for i := 0; i < h; i++ {
		res = append(res, cyc[2*i]+1)
	}

	return 1, res
}

func max(a, b int) int {
	if a >= b {
		return a
	}
	return b
}

func getGraph(n int, E [][]int) [][]int {
	degree := make([]int, n)
	for _, e := range E {
		u, v := e[0]-1, e[1]-1
		degree[u]++
		degree[v]++
	}
	adj := make([][]int, n)
	for i := 0; i < n; i++ {
		adj[i] = make([]int, 0, degree[i])
	}

	for _, e := range E {
		u, v := e[0]-1, e[1]-1
		adj[u] = append(adj[u], v)
		adj[v] = append(adj[v], u)
	}

	return adj
}


package main

import (
	"bufio"
	"bytes"
	"fmt"
	"os"
	"strconv"
)

func readInt(bytes []byte, from int, val *int) int {
	i := from
	sign := 1
	if bytes[i] == '-' {
		sign = -1
		i++
	}
	tmp := 0
	for i < len(bytes) && bytes[i] >= '0' && bytes[i] <= '9' {
		tmp = tmp*10 + int(bytes[i]-'0')
		i++
	}
	*val = tmp * sign
	return i
}

func readNum(reader *bufio.Reader) (a int) {
	bs, _ := reader.ReadBytes('\n')
	readInt(bs, 0, &a)
	return
}

func readTwoNums(reader *bufio.Reader) (a int, b int) {
	res := readNNums(reader, 2)
	a, b = res[0], res[1]
	return
}

func readThreeNums(reader *bufio.Reader) (a int, b int, c int) {
	res := readNNums(reader, 3)
	a, b, c = res[0], res[1], res[2]
	return
}

func readNNums(reader *bufio.Reader, n int) []int {
	res := make([]int, n)
	x := 0
	bs, _ := reader.ReadBytes('\n')
	for i := 0; i < n; i++ {
		for x < len(bs) && (bs[x] < '0' || bs[x] > '9') && bs[x] != '-' {
			x++
		}
		x = readInt(bs, x, &res[i])
	}
	return res
}

func readUint64(bytes []byte, from int, val *uint64) int {
	i := from

	var tmp uint64
	for i < len(bytes) && bytes[i] >= '0' && bytes[i] <= '9' {
		tmp = tmp*10 + uint64(bytes[i]-'0')
		i++
	}
	*val = tmp

	return i
}

func main() {
	reader := bufio.NewReader(os.Stdin)

	firstLine := readNNums(reader, 3)
	n, m, k := firstLine[0], firstLine[1], firstLine[2]
	E := make([][]int, m)
	for i := 0; i < m; i++ {
		E[i] = readNNums(reader, 2)
	}
	t, res := solve(n, k, E)

	fmt.Println(t)

	if t == 2 {
		fmt.Println(len(res))
	}

	var buf bytes.Buffer

	for i := 0; i < len(res); i++ {
		buf.WriteString(strconv.Itoa(res[i]))
		if i < len(res)-1 {
			buf.WriteByte(' ')
		}
	}
	fmt.Println(buf.String())
}

func solve(n, k int, E [][]int) (int, []int) {
	G := getGraph(n, E)
	pos := make([]int, n)
	for i := 0; i < n; i++ {
		pos[i] = -1
	}
	cnt := make([]int, 2)
	vis := make([]int, 0, n)
	cyc := make([]int, 0, n)
	var dfs func(u int) bool

	dfs = func(u int) bool {
		pos[u] = len(vis)
		cnt[pos[u]&1]++
		vis = append(vis, u)
		var low = -1
		for _, v := range G[u] {
			// visited before and not parent
			if pos[v] >= 0 && pos[u] > pos[v]+1 {
				low = max(low, pos[v])
			}
		}

		if low >= 0 {
			for i := low; i <= pos[u]; i++ {
				cyc = append(cyc, vis[i])
			}
			return true
		}

		for _, v := range G[u] {
			if pos[v] < 0 {
				if dfs(v) {
					return true
				}
			}
		}
		vis = vis[:pos[u]]
		return false
	}

	hasCycle := dfs(0)

	res := make([]int, 0, n)
	h := (k + 1) / 2

	if !hasCycle {
		var x int
		if cnt[1] > cnt[0] {
			x = 1
		}
		for i := 0; i < n; i++ {
			if pos[i]&1 == x {
				res = append(res, i+1)
			}
		}
		return 1, res[:h]
	}

	if len(cyc) <= k {
		for i := 0; i < len(cyc); i++ {
			res = append(res, cyc[i]+1)
		}
		return 2, res
	}

	for i := 0; i < h; i++ {
		res = append(res, cyc[2*i]+1)
	}

	return 1, res
}

func max(a, b int) int {
	if a >= b {
		return a
	}
	return b
}

func getGraph(n int, E [][]int) [][]int {
	degree := make([]int, n)
	for _, e := range E {
		u, v := e[0]-1, e[1]-1
		degree[u]++
		degree[v]++
	}
	adj := make([][]int, n)
	for i := 0; i < n; i++ {
		adj[i] = make([]int, 0, degree[i])
	}

	for _, e := range E {
		u, v := e[0]-1, e[1]-1
		adj[u] = append(adj[u], v)
		adj[v] = append(adj[v], u)
	}

	return adj
}


package main

import (
	"bufio"
	"fmt"
	"os"
)

type node struct {
	diff int
	time int
}

type ByTime []node

func (a ByTime) Len() int           { return len(a) }
func (a ByTime) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a ByTime) Less(i, j int) bool { return a[i].time < a[j].time }

var from []int
var depth []int
var ans []int
var best int
var edges [][]int
var n, m, k int
var visited []bool
var color []int
var prev []int
var found bool

func main() {
	in := bufio.NewReader(os.Stdin)
	out := bufio.NewWriter(os.Stdout)
	defer out.Flush()
	t := int64(1)
	//fmt.Fscanf(in, "%d\n", &t)
	for tt := int64(0); tt < t; tt++ {
		n, m, k = 0, 0, 0
		fmt.Fscanf(in, "%d %d %d\n", &n, &m, &k)
		edges = make([][]int, n)
		for i := 0; i < m; i++ {
			o, p := 0, 0
			fmt.Fscanf(in, "%d %d\n", &o, &p)
			o--
			p--
			//fmt.Println(o, p, edges)
			edges[o] = append(edges[o], p)
			edges[p] = append(edges[p], o)
		}
		best = -1
		visited = make([]bool, n)
		depth = make([]int, n)
		dfs(0, 0, -1)
		if best == -1 {
			color = make([]int, n)
			// color the tree
			colorTree(0, 1)
			g1 := make([]int, 0)
			g2 := make([]int, 0)
			for i := 0; i < n; i++ {
				if color[i] == 1 {
					g1 = append(g1, i)
				} else {
					g2 = append(g2, i)
				}
			}
			if len(g1) < len(g2) {
				g1 = g2
			}
			g1 = g1[:(k+1)/2]
			fmt.Fprintf(out, "1\n")
			for i := 0; i < len(g1)-1; i++ {
				fmt.Fprintf(out, "%d ", g1[i]+1)
			}
			fmt.Fprintf(out, "%d\n", g1[len(g1)-1]+1)
		} else {
			prev = make([]int, n)
			found = false
			ans = make([]int, 0)
			visited = make([]bool, n)
			depth = make([]int, n)
			//fmt.Println("best", best)
			dfs2(0, 0, -1)
			if best <= k {
				fmt.Fprintf(out, "2\n")
				fmt.Fprintf(out, "%d\n", best)

				for i := 0; i < best-1; i++ {
					fmt.Fprintf(out, "%d ", ans[i]+1)
				}
				fmt.Fprintf(out, "%d\n", ans[best-1]+1)
			} else {
				newAns := []int{}
				for i := 0; i < len(ans); i++ {
					if i%2 == 0 {
						newAns = append(newAns, ans[i])
					}
				}
				ans = newAns
				//fmt.Println("debug2", ans)
				ans = ans[:(k+1)/2]
				fmt.Fprintf(out, "1\n")
				for i := 0; i < len(ans)-1; i++ {
					fmt.Fprintf(out, "%d ", ans[i]+1)
				}
				fmt.Fprintf(out, "%d\n", ans[len(ans)-1]+1)
			}
		}
	}
	return
}

func dfs(i int, curDepth int, from int) {
	//fmt.Println("dfs", i, curDepth, from)
	if visited[i] {
		//fmt.Println("debug", best)
		if best == -1 || curDepth-depth[i] < best {
			//fmt.Println("debug")
			best = curDepth - depth[i]
		}
		return
	}
	visited[i] = true
	depth[i] = curDepth
	for _, next := range edges[i] {
		if next != from {
			dfs(next, curDepth+1, i)
		}
	}
	//visited[i] = false
}

func dfs2(i int, curDepth int, from int) {
	//fmt.Println("dfs2debug1", i, curDepth, from)

	if found {
		return
	}
	if visited[i] {
		if curDepth-depth[i] == best {
			ans = append(ans, i)
			tmp := prev[i]
			for tmp != i {
				ans = append(ans, tmp)
				tmp = prev[tmp]
			}
			found = true
		}
		return
	}
	visited[i] = true
	depth[i] = curDepth

	for _, next := range edges[i] {
		//fmt.Println("dfs2debug2", i, curDepth, from, next)

		if next != from {
			prev[next] = i
			dfs2(next, curDepth+1, i)
		}
	}
	//visited[i] = false
}

func colorTree(i int, curColor int) {
	//fmt.Println("color", i, curColor)
	if color[i] != 0 {
		return
	}
	color[i] = curColor
	for _, edge := range edges[i] {
		colorTree(edge, 3-curColor)
	}
}