macro_rules ! input { ( source = $ s : expr , $ ( $ r : tt ) * ) => { let mut iter = $ s . split_whitespace ( ) ; input_inner ! { iter , $ ( $ r ) * } } ; ( iter = $ iter : ident , $ ( $ 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 ) * } } ; ( $ ( $ 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 , mut $ var : ident : $ t : tt $ ( $ r : tt ) * ) => { let mut $ var = read_value ! ( $ iter , $ t ) ; input_inner ! { $ iter $ ( $ r ) * } } ; ( $ iter : expr , mut $ var : ident $ ( $ r : tt ) * ) => { input_inner ! { $ iter , mut $ var : usize $ ( $ r ) * } } ; ( $ iter : expr , $ var : ident : $ t : tt $ ( $ r : tt ) * ) => { let $ var = read_value ! ( $ iter , $ t ) ; input_inner ! { $ iter $ ( $ r ) * } } ; ( $ iter : expr , $ var : ident $ ( $ r : tt ) * ) => { input_inner ! { $ iter , $ var : usize $ ( $ 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 : $ base : expr } ) => { read_value ! ( $ iter , String ) . chars ( ) . map ( | c | ( c as u8 - $ base as u8 ) as usize ) . collect ::< Vec < usize >> ( ) } ; ( $ iter : expr , { char : $ base : expr } ) => { read_value ! ( $ iter , { chars : $ base } ) [ 0 ] } ; ( $ iter : expr , chars ) => { read_value ! ( $ iter , String ) . chars ( ) . collect ::< Vec < char >> ( ) } ; ( $ iter : expr , char ) => { read_value ! ( $ iter , chars ) [ 0 ] } ; ( $ iter : expr , usize1 ) => { read_value ! ( $ iter , usize ) - 1 } ; ( $ iter : expr , $ t : ty ) => { $ iter . next ( ) . unwrap ( ) . parse ::<$ t > ( ) . unwrap ( ) } ; }
const MOD: i64 = 998_244_353;
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct Modi64(pub i64);
impl Modi64 {
    #[inline]
    pub fn new(i: i64) -> Modi64 {
        Modi64(Modi64::modulo(i, MOD))
    }
    #[inline]
    pub fn modulo(i: i64, m: i64) -> i64 {
        if i < 0 {
            i % m + m
        } else {
            i % m
        }
    }
    pub fn pow(self, y: i64) -> Modi64 {
        let mut y = Modi64(Modi64::modulo(y, MOD - 1));
        let mut x = 1.into();
        let mut base = self;
        while y.0 > 0 {
            if y.0 & 1 == 1 {
                x *= base;
            }
            base *= base;
            y = Modi64(y.0 >> 1);
        }
        x
    }
    #[inline]
    pub fn inv(self) -> Modi64 {
        self.pow(MOD - 2)
    }
}
impl From<i64> for Modi64 {
    fn from(i: i64) -> Modi64 {
        Modi64::new(i)
    }
}
impl std::ops::Add for Modi64 {
    type Output = Modi64;
    fn add(self, rhs: Modi64) -> Modi64 {
        (self.0 + rhs.0).into()
    }
}
impl std::ops::AddAssign for Modi64 {
    fn add_assign(&mut self, rhs: Modi64) {
        *self = *self + rhs;
    }
}
impl std::ops::Sub for Modi64 {
    type Output = Modi64;
    fn sub(self, rhs: Modi64) -> Modi64 {
        (self.0 - rhs.0).into()
    }
}
impl std::ops::SubAssign for Modi64 {
    fn sub_assign(&mut self, rhs: Modi64) {
        *self = *self - rhs;
    }
}
impl std::ops::Mul for Modi64 {
    type Output = Modi64;
    fn mul(self, rhs: Modi64) -> Modi64 {
        (self.0 * rhs.0).into()
    }
}
impl std::ops::MulAssign for Modi64 {
    fn mul_assign(&mut self, rhs: Modi64) {
        *self = *self * rhs;
    }
}
impl std::ops::Div for Modi64 {
    type Output = Modi64;
    fn div(self, rhs: Modi64) -> Modi64 {
        self * rhs.inv()
    }
}
impl std::ops::DivAssign for Modi64 {
    fn div_assign(&mut self, rhs: Modi64) {
        *self = *self / rhs;
    }
}
impl std::ops::Neg for Modi64 {
    type Output = Modi64;
    fn neg(self) -> Modi64 {
        (-self.0).into()
    }
}
impl std::iter::Sum for Modi64 {
    fn sum<I: Iterator<Item = Modi64>>(iter: I) -> Modi64 {
        iter.fold(0.into(), std::ops::Add::add)
    }
}
impl<'a> std::iter::Sum<&'a Modi64> for Modi64 {
    fn sum<I: Iterator<Item = &'a Modi64>>(iter: I) -> Modi64 {
        iter.fold(0.into(), |x, &y| x + y)
    }
}
impl std::fmt::Display for Modi64 {
    fn fmt<'a>(&self, f: &mut std::fmt::Formatter<'a>) -> Result<(), std::fmt::Error> {
        write!(f, "{}", self.0)
    }
}
#[derive(Clone, Copy, Debug, Default)]
pub struct Adjacent {
    pub to: usize,
    pub id: usize,
}
impl Adjacent {
    pub fn new(to: usize, id: usize) -> Adjacent {
        Adjacent { to: to, id: id }
    }
}
#[derive(Clone, Debug, Default)]
pub struct Graph {
    pub vsize: usize,
    pub esize: usize,
    pub graph: Vec<Vec<Adjacent>>,
}
impl Graph {
    pub fn new(vsize: usize) -> Graph {
        Graph {
            vsize: vsize,
            esize: 0,
            graph: vec![vec![]; vsize],
        }
    }
    pub fn add_edge(&mut self, from: usize, to: usize) {
        self.graph[from].push(Adjacent::new(to, self.esize));
        self.esize += 1;
    }
    pub fn add_undirected_edge(&mut self, u: usize, v: usize) {
        self.graph[u].push(Adjacent::new(v, self.esize));
        self.graph[v].push(Adjacent::new(u, self.esize));
        self.esize += 1;
    }
    pub fn vertexes(&self) -> std::ops::Range<usize> {
        0..self.vsize
    }
    pub fn adjacency(&self, from: usize) -> &Vec<Adjacent> {
        &self.graph[from]
    }
    pub fn grundy(&self) -> Vec<usize> {
        let mut res = vec![0; self.vsize];
        for u in self.vertexes().rev() {
            let mut set = std::collections::HashSet::new();
            for v in self.adjacency(u) {
                if u < v.to {
                    set.insert(res[v.to]);
                }
            }
            for i in 0.. {
                if !set.contains(&i) {
                    res[u] = i;
                    break;
                }
            }
        }
        res
    }
}
fn main() {
    input! {
        n,
        m1, e1: [(usize1, usize1); m1],
        m2, e2: [(usize1, usize1); m2],
        m3, e3: [(usize1, usize1); m3]
    };
    let mut g1 = Graph::new(n);
    let mut g2 = Graph::new(n);
    let mut g3 = Graph::new(n);
    for &(u, v) in e1.iter() {
        g1.add_undirected_edge(u, v);
    }
    for &(u, v) in e2.iter() {
        g2.add_undirected_edge(u, v);
    }
    for &(u, v) in e3.iter() {
        g3.add_undirected_edge(u, v);
    }
    let pow10_18 = Modi64(10).pow(18);
    let mut tb = vec![Modi64(1); n + 1];
    for i in 0..n {
        tb[i + 1] = tb[i] * pow10_18;
    }
    let p1 = g1.grundy();
    let p2 = g2.grundy();
    let p3 = g3.grundy();
    let mut q1 = std::collections::HashMap::new();
    let mut q2 = std::collections::HashMap::new();
    let mut q3 = std::collections::HashMap::new();
    for i in 0..n {
        *q1.entry(p1[i]).or_insert(Modi64(0)) += tb[i + 1];
        *q2.entry(p2[i]).or_insert(Modi64(0)) += tb[i + 1];
        *q3.entry(p3[i]).or_insert(Modi64(0)) += tb[i + 1];
    }
    let mut ans = Modi64(0);
    for (&k1, &c1) in q1.iter() {
        for (&k2, &c2) in q2.iter() {
            for (&k3, &c3) in q3.iter() {
                if k1 ^ k2 ^ k3 == 0 {
                    ans += c1 * c2 * c3;
                }
            }
        }
    }
    println!("{}", ans);
}


Measured memory_bytes: 47352