using System;
using System.Collections;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Collections.Generic;
using System.Threading.Tasks;

/*
・概要
：FordFulkersonのアルゴリズムを使用し、（重みなし）最大二部マッチング問題を解くライブラリ。
	（最大流問題に使用するアルゴリズムを応用）

：最大流を求める計算量は、O(f * E)
	（最大フローがf、辺の数がE）
	なお、事前準備の段階でO(X + Y)かかる。
	（X,Yは二集団それぞれの要素数。）

・（最大）二部マッチング問題とは。
：最大二部マッチング
	「二集団間で最大何ペア作ることができるか」、という問題。
	（ペア作成可能）条件を満たすペアの間にだけ、有向辺を追加し、
	後は最大流問題として解くだけ。

：重み付き最大二部マッチング
	上記に加え、ペア毎に異なる利得が得られる場合に、
	「全ペアの合計利得の最大値」を求める問題。
	→「最小費用流」問題に帰着可能（本ライブラリの対象外）

：最大流問題を応用した解法
	二集団の前後にノード（スタートとゴール）を付け加えることで、最大流問題に帰着可能
	（cf. 重みづけがないので、フローを全て1にすれば良いだけ）

・使用法
1. 名前空間FordFulkerson上のクラスMatchingのインスタンスを作成
	引数は、２集団X,Yそれぞれの要素数（頂点の数）
	（e.g. FordFulkerson.Matching match = new FordFulkerson.Matching(X,Y);）

2. MatchPair関数で、マッチング可能な二者間全ての間に、辺を形成する。
	引数は、集団Xの頂点と、集団Yの頂点。
	（e.g. match.MatchPair(x,y);）
	なお、それぞれ「Xの中で何番目か」、「Yの中で何番目か」を渡せば良い。

3. MaxPair関数で、最大マッチング数を取得する。
	（e.g. int ans = match.MaxPair();）

*/

class Program
{
	//Main
	static void Main(string[] args)
	{
		//読み込む
		int N = int.Parse(Console.ReadLine());

		//それぞれの座標を読み込む
		int[][] red = new int[N][];
		int[][] blue = new int[N][];
		for(int i=0; i<N; i++){
			red[i] = Console.ReadLine().Split(' ').Select(j => int.Parse(j)).ToArray();
		}
		for(int i=0; i<N; i++){
			blue[i] = Console.ReadLine().Split(' ').Select(j => int.Parse(j)).ToArray();
		}

		//1. 名前空間FordFulkerson上のクラスMatchingのインスタンスを作成
		//引数は、２集団X,Yそれぞれの要素数（頂点の数）
		FordFulkerson.Matching match = new FordFulkerson.Matching(N,N);

		//2. マッチング可能な二者間全ての間に、辺を形成する。
		for(int i=0; i<N; i++){
			for(int j=0; j<N; j++){
				if(red[i][0] < blue[j][0] && red[i][1] < blue[j][1]){
					match.MatchPair(i,j);
				}
			}
		}

		//MaxPair関数で、最大マッチング数を取得する。
		int ans = match.MaxPair();

		Console.WriteLine(ans);
	}
}

//FordFulkerson法用の名前空間
/*
3つのクラスを内包
・（重みなし）最大二部マッチング問題を解く為のクラスMatching
・辺を意味するEdge
・グラフとして問題を解くGraph
*/

/*
全てclass Graphからアクセスのこと
（e.g.
・初期化
	FordFulkerson.Graph maxPair = new FordFulkerson.Graph(N+N+2);

・エッジの追加
	maxPair.AddEdge(start,goal,1);
）
*/

namespace FordFulkerson
{
	//最大二部マッチング問題を解く為のクラス
	class Matching
	{
		//マッチング問題を解くのに使うGraph
		private Graph graph;
		//集団X、集団Yそれぞれの数
		private int X;
		private int Y;
		//graphの要素数
		private int len;

		//コンストラクタ
		/*
		引数は、集団1、集団2それぞれの要素数（頂点の数）
		（e.g. 男女でペアを作る場合、男子の数と女子の数）
		*/
		public Matching(int x, int y)
		{
			//要素数を設定
			this.X = x;
			this.Y = y;

			//graphを初期化
			//1. インスタンスを作成
			//ノード数は、両集団の構成員数＋２（スタートとゴール）
			this.len = X + Y + 2;
			this.graph = new Graph(len);

			//2. スタートと集団Xとの間、集団Yとゴールとの間に、それぞれ辺を作成
			this.Initialize();
		}

		//graphを初期化する関数（計算量O（X + Y））
		/*
		スタートと集団Xとの間、集団Yとゴールとの間に、それぞれ辺を作成。
		これにより、スタートからゴールまでの経路が作成可能なようにする。
		（重みは全て1で良い。スタート・ゴールと結んだ辺の数を、ペア数が超えることはない。）
		*/
		private void Initialize()
		{
			/*
			スタート、ゴールのノード番号はそれぞれ、
			要素数の末尾2つとする。（len-2, len-1）
			*/
			int start = this.len-2;
			int goal = this.len-1;

			//集団Xの全要素をスタートに結合
			for(int i=0; i<this.X; i++){
				/*
				集団Xのノード番号は、そのままiを使う
				（i：集団X内で何番目か）
				*/
				this.graph.AddEdge(start,i,1);
			}

			//集団Yの全要素をゴールに結合
			for(int i=0; i<this.Y; i++){
				/*
				集団Yのノード番号は、i+Xとなる
				（i：集団Y内で何番目か）
				*/
				this.graph.AddEdge(i+this.X,goal,1);
			}
		}

		//マッチング可能なペアの間に、辺を形成する関数
		/*
		引数は、
		1.集団Xの中の何番目の要素か、
		2.集団Xの中の何番目の要素か、
		*/
		public void MatchPair(int elemX, int elemY)
		{
			//両者の間に辺を結ぶ
			/*
			集団Xのノード番号は、そのままelemXを使う。
			集団Yのノード番号は、elemY+Xとなる
			*/
			this.graph.AddEdge(elemX,elemY+this.X,1);
		}

		//最大マッチング数を返す関数
		public int MaxPair()
		{
			//スタートからゴールまでの最大流を返せば良い
			int start = this.len-2;
			int goal = this.len-1;
			return this.graph.MaxFlow(start,goal);
		}
	}

	class Edge
	{
	  public Edge(int begin, int end, int weight)
	  {
	    this.Begin = begin;
	    this.End = end;
	    this.Weight = weight;
	  }

	  public int Begin { get; private set; }
	  public int End { get; private set; }
	  public int Weight { get; private set; }

	  public override string ToString()
	  {
	    return string.Format(
	      "Begin[{0}], End[{1}], Weight[{2}]",
	      Begin, End, Weight);
	  }
	}

	class Graph
	{
	  private Dictionary<int, List<Edge>> _adjacentEdges
	    = new Dictionary<int, List<Edge>>();

		//コンストラクタ
		/*
		全体のノード数で初期化
		*/
	  public Graph(int vertexCount)
	  {
	    this.VertexCount = vertexCount;
	  }

	  public int VertexCount { get; private set; }

	  public IEnumerable<int> Vertices
	  {
	    get
	    {
	      return _adjacentEdges.Keys;
	    }
	  }

	  public IEnumerable<Edge> Edges
	  {
	    get
	    {
	      return _adjacentEdges.Values.SelectMany(e => e);
	    }
	  }

	  public int EdgeCount
	  {
	    get
	    {
	      return this.Edges.Count();
	    }
	  }

		//辺を追加
		/*
		引数：出発点、行き先、重み（その辺の最大流）
		（＝始点、終点、フロー）
		*/
	  public void AddEdge(int begin, int end, int weight)
	  {
	    if (!_adjacentEdges.ContainsKey(begin))
	    {
	      var edges = new List<Edge>();
	      _adjacentEdges.Add(begin, edges);
	    }

	    _adjacentEdges[begin].Add(new Edge(begin, end, weight));
	  }

		//最大流を返す
		/*
		引数：出発点、行き先（スタートとゴール）
		*/
	  public int MaxFlow(int s, int t)
	  {
	    int u, v;

	    // Create a residual graph and fill the residual graph with
	    // given capacities in the original graph as residual capacities
	    // in residual graph
	    int[,] residual = new int[VertexCount, VertexCount];

	    // Residual graph where rGraph[i,j] indicates
	    // residual capacity of edge from i to j (if there
	    // is an edge. If rGraph[i,j] is 0, then there is not)
	    for (u = 0; u < VertexCount; u++)
	      for (v = 0; v < VertexCount; v++)
	        residual[u, v] = 0;
	    foreach (var edge in this.Edges)
	    {
	      residual[edge.Begin, edge.End] = edge.Weight;
	    }

	    // This array is filled by BFS and to store path
	    int[] parent = new int[VertexCount];

	    // There is no flow initially
	    int maxFlow = 0;

	    // Augment the flow while there is path from source to sink
	    while (BFS(residual, s, t, parent))
	    {
	      // Find minimum residual capacity of the edhes along the
	      // path filled by BFS. Or we can say find the maximum flow
	      // through the path found.
	      int pathFlow = int.MaxValue;
	      for (v = t; v != s; v = parent[v])
	      {
	        u = parent[v];
	        pathFlow = pathFlow < residual[u, v]
	          ? pathFlow : residual[u, v];
	      }

	      // update residual capacities of the edges and reverse edges
	      // along the path
	      for (v = t; v != s; v = parent[v])
	      {
	        u = parent[v];
	        residual[u, v] -= pathFlow;
	        residual[v, u] += pathFlow;
	      }

	      // Add path flow to overall flow
	      maxFlow += pathFlow;
	    }

	    // Return the overall flow
	    return maxFlow;
	  }

	  // Returns true if there is a path from source 's' to sink 't' in
	  // residual graph. Also fills parent[] to store the path.
	  private bool BFS(int[,] residual, int s, int t, int[] parent)
	  {
	    bool[] visited = new bool[VertexCount];
	    for (int i = 0; i < visited.Length; i++)
	    {
	      visited[i] = false;
	    }

	    Queue<int> q = new Queue<int>();

	    visited[s] = true;
	    q.Enqueue(s);
	    parent[s] = -1;

	    // standard BFS loop
	    while (q.Count > 0)
	    {
	      int u = q.Dequeue();

	      for (int v = 0; v < VertexCount; v++)
	      {
	        if (!visited[v]
	          && residual[u, v] > 0)
	        {
	          q.Enqueue(v);
	          visited[v] = true;
	          parent[v] = u;
	        }
	      }
	    }

	    // If we reached sink in BFS starting from source,
	    // then return true, else false
	    return visited[t] == true;
	  }
	}
}



Measured memory_bytes: 19680