fn main() {
	let mut s = String::new();
	use std::io::Read;
	std::io::stdin().read_to_string(&mut s).unwrap();
	let mut s = s.split_whitespace();
	let n: usize = s.next().unwrap().parse().unwrap();
	let mut graph = Graph::new(2 * n + 2);
	let red = (0..n)
		.map(|_| {
			(
				s.next().unwrap().parse::<u8>().unwrap(),
				s.next().unwrap().parse::<u8>().unwrap(),
			)
		})
		.collect::<Vec<_>>();
	let blue = (0..n)
		.map(|_| {
			(
				s.next().unwrap().parse::<u8>().unwrap(),
				s.next().unwrap().parse::<u8>().unwrap(),
			)
		})
		.collect::<Vec<_>>();
	let from = 2 * n;
	let to = 2 * n + 1;
	for i in 0..n {
		graph.add_edge(from, i, 1);
		graph.add_edge(n + i, to, 1);
		for j in 0..n {
			if red[i].0 < blue[j].0 && red[i].1 < blue[j].1 {
				graph.add_edge(i, n + j, 1);
			}
		}
	}
	let ans = graph.max_flow(from, to);
	println!("{}", ans);
}
#[derive(Clone, Copy)]
pub struct Edge<T> {
	dst: u32,
	capacity: T,
	flow: T,
	reverse: u32,
}

impl<T> Edge<T> {
	pub fn new(dst: u32, capacity: T, flow: T, reverse: u32) -> Self {
		Self {
			dst,
			capacity,
			flow,
			reverse,
		}
	}
}

pub struct Graph<T> {
	edges: Vec<Vec<Edge<T>>>,
}

impl<T> Graph<T>
where
	T: Clone + Ord + num::Signed + num::traits::Bounded,
{
	pub fn new(n: usize) -> Self {
		Self {
			edges: vec![vec![]; n],
		}
	}

	pub fn size(&self) -> usize {
		self.edges.len()
	}

	pub fn add_edge(&mut self, src: usize, dst: usize, capacity: T) {
		let dstid = self.edges[dst as usize].len() as u32;
		let srcid = self.edges[src as usize].len() as u32;
		self.edges[src as usize].push(Edge::new(dst as u32, capacity, T::zero(), dstid));
		self.edges[dst as usize].push(Edge::new(src as u32, T::zero(), T::zero(), srcid));
	}

	fn bfs(&self, s: usize, t: usize, level: &mut Vec<i32>) -> bool {
		for l in level.iter_mut() {
			*l = -1;
		}
		level[s] = 0;
		let mut q = std::collections::VecDeque::new();
		q.push_back(s as u32);
		while let Some(v) = q.pop_front() {
			for e in self.edges[v as usize].iter() {
				if e.flow < e.capacity && level[e.dst as usize] < 0 {
					level[e.dst as usize] = level[v as usize] + 1;
					q.push_back(e.dst);
				}
			}
		}
		level[t] >= 0
	}

	fn dfs(&mut self, v: usize, t: usize, flow: T, level: &[i32], iter: &mut Vec<u32>) -> T {
		if v == t {
			return flow;
		}
		while (iter[v] as usize) < self.edges[v].len() {
			let i = iter[v] as usize;
			let e = self.edges[v][i].clone();
			if e.capacity > e.flow && level[v] + 1 == level[e.dst as usize] {
				let d = self.dfs(
					e.dst as usize,
					t,
					std::cmp::min(flow.clone(), e.capacity.clone() - e.flow.clone()),
					level,
					iter,
				);
				if d > T::zero() {
					self.edges[v][i].flow = self.edges[v][i].flow.clone() + d.clone();
					self.edges[e.dst as usize][e.reverse as usize].flow =
						self.edges[e.dst as usize][e.reverse as usize].flow.clone() - d.clone();
					return d;
				}
			}
			iter[v] += 1;
		}
		T::zero()
	}

	pub fn max_flow(&mut self, s: usize, t: usize) -> T {
		let mut r = T::zero();
		let mut level = vec![-1; self.size()];
		while self.bfs(s, t, &mut level) {
			let mut iter = vec![0; self.size()];
			loop {
				let flow = self.dfs(s, t, T::max_value(), &level, &mut iter);
				if flow > T::zero() {
					r = r + flow;
				} else {
					break;
				}
			}
		}
		r
	}
}


Measured memory_bytes: 2308