// Returns the size of maximum bipartite matching.
// Complexity: O(V E)
// Args:
//   lSize: Number of vertexes of the left set.
//   rSize: Number of vertexes of the right set.
//   lGraph: Adjacency graph from the left to the right.
fun bipartiteMatching(lSize: Int, rSize: Int, lGraph: Array<MutableList<Int>>): Int {
  var matchSize = 0
  val lMatches = IntArray(lSize, { -1 })
  val rMatches = IntArray(rSize, { -1 })
  val rSearched = BooleanArray(rSize)

  fun search(lv: Int): Boolean {
    for (rv in lGraph[lv]) {
      if (rSearched[rv]) continue
      rSearched[rv] = true
      if (rMatches[rv] == -1 || search(rMatches[rv])) {
        lMatches[lv] = rv
        rMatches[rv] = lv
        return true
      }
    }
    return false
  }

  for (lv in 0 until lSize) {
    rSearched.fill(false)
    if (search(lv)) matchSize++
  }
  return matchSize
}

data class Int2D(val x: Int, val y: Int)

fun main(args: Array<String>) {
  val n = readLine()!!.toInt()
  val lPoints = Array(n, {
    val (x, y) = readLine()!!.split(" ").map(String::toInt)
    Int2D(x, y)
  })
  val rPoints = Array(n, {
    val (x, y) = readLine()!!.split(" ").map(String::toInt)
    Int2D(x, y)
  })
  val lGraph = Array(n, { mutableListOf<Int>() })
  for (lv in 0 until n) {
    for (rv in 0 until n) {
      if (lPoints[lv].x < rPoints[rv].x && lPoints[lv].y < rPoints[rv].y) {
        lGraph[lv].add(rv)
      }
    }
  }
  val ans = bipartiteMatching(n, n, lGraph)
  println(ans)
}
