#include <bits/stdc++.h>
using namespace std;
 
using ll = long long;
using ld  = long double;
using pll = pair<ll, ll> ;
using pld = pair<ld, ld>;
const int INF=1e9+7;
const ll LINF=1LL<<60;
const ll MOD=1e9+7;
const ld PI=acos(-1);
const ld EPS = 1e-9; //微調整用（EPSより小さいと0と判定など）

//MODINT
//変数名.nでint型の数値を受け取る
struct mint {
    int n;
    mint(int n_ = 0) : n(n_) {}
};
 
mint operator+(mint a, mint b) { a.n += b.n; if (a.n >= MOD) a.n -= MOD; return a; }
mint operator-(mint a, mint b) { a.n -= b.n; if (a.n < 0) a.n += MOD; return a; }
mint operator*(mint a, mint b) { return (long long)a.n * b.n % MOD; }
mint &operator+=(mint &a, mint b) { return a = a + b; }
mint &operator-=(mint &a, mint b) { return a = a - b; }
mint &operator*=(mint &a, mint b) { return a = a * b; }

int ii() { int x; scanf("%d", &x); return x; }
long long il() { long long x; scanf("%lld", &x); return x; }
string is() { string x; cin >> x; return x; }
char ic() { char x; cin >> x; return x; }
void oi(int x) { printf("%d ", x); }
void ol(long long x) { printf("%lld ", x); }
void od(double x) { printf("%.15f ", x); }
void os(const string &s) { printf("%s ", s.c_str()); }
void oc(const char &c) { printf("%c ", c); }
auto op = [&](auto p) -> void{ cout << p; };
auto ov = [&](auto vec) -> void{ cout << vec; };
void br() { putchar('\n'); }
 
#define gcd __gcd //llは受け取ってくれない
int lcm(int a, int b){return a / gcd(a, b) * b;}
#define ALL(a) a.begin(),a.end() //sort(ALL(vec));
#define REP(i,m,n) for(int i=(int)(m) ; i < (int) (n) ; ++i )
#define rep(i,n) REP(i,0,n)
#define MP(a,b) make_pair(a,b)
#define SORT_UNIQUE(c) (sort(c.begin(),c.end()), c.resize(distance(c.begin(),unique(c.begin(),c.end()))))
#define GET_POS(c,x) (lower_bound(c.begin(),c.end(),x)-c.begin())
#define PB push_back
#define SZ(x) ((int)(x).size) //size()がunsignedなのでエラー避けに

// coutによるpairの出力（空白区切り）
template<typename T1, typename T2> ostream& operator<<(ostream& s, const pair<T1, T2>& p) {return s << "(" << p.first << " " << p.second << ")";}
// 空白区切りだけ --> return s << "(" << p.first << " " << p.second << ")";
// 見やすくしたいとき --> return s << "(" << p.first << " " << p.second << ")";
// coutによるvectorの出力（空白区切り）
template<typename T> ostream& operator<<(ostream& s, const vector<T>& v) {
	int len = v.size();
	for (int i = 0; i < len; ++i) {
		s << v[i]; if (i < len - 1) s << " "; //"\t"に変えるとTabで見やすく区切る
	}
	return s;
}
// coutによる多次元vectorの出力（空白区切り）
template<typename T> ostream& operator<<(ostream& s, const vector< vector<T> >& vv) {
	int len = vv.size();
	for (int i = 0; i < len; ++i) {
		s << vv[i] << endl;
	}
	return s;
}

//最大値、最小値を更新する。aよりbのが大きい（小さい）か等しければaを更新してtrueを返す。そうでなければ何もせずfalseを返す chmax(nowmax,x);
template<typename T>
bool chmax(T& a, T b){return (a = max(a, b)) == b;}
template<typename T>
bool chmin(T& a, T b){return (a = min(a, b)) == b;}

// ----- template end ---- //
 
// ------- library ------- //


//初期設定
typedef int FLOW;                // フローを表す型、今回は int 型
const int MAX_V = 202;           // グラフの最大ノード数
const FLOW FLOW_INF = INF;      // 十分大きい値を入れる

// グラフの辺の構造体 ... addedgeで貼るので、構造は気にしなくていいか？
struct Edge {
  int rev, from, to;
  FLOW cap, icap;
  Edge(int r, int f, int t, FLOW c) : rev(r), from(f), to(t), cap(c), icap(c) {}
  friend ostream& operator << (ostream& s, const Edge& E) {
    if (E.cap > 0) return s << E.from << "->" << E.to << '(' << E.cap << ')';
    else return s;
  }
};

// グラフ構造体
// 宣言 Graph graph(Agroupノード数 + Bgroupノード数 + 2); //+2は追加するS, T
// .addedge(from, to, edgeの最大容量)で使う
struct Graph {
  int V;
  vector<Edge> list[MAX_V];

  Graph(int n = 0) : V(n) { for (int i = 0; i < MAX_V; ++i) list[i].clear(); }
  void init(int n = 0) { V = n; for (int i = 0; i < MAX_V; ++i) list[i].clear(); }
  void resize(int n = 0) { V = n; }
  void reset() { for (int i = 0; i < V; ++i) for (int j = 0; j < list[i].size(); ++j) list[i][j].cap = list[i][j].icap; }
  inline vector<Edge>& operator [] (int i) { return list[i]; }

  Edge &redge(Edge e) {
    if (e.from != e.to) return list[e.to][e.rev];
    else return list[e.to][e.rev + 1];
  }

  //cap はcapability(edgeが運べる最大容量)？
  void addedge(int from, int to, FLOW cap) {
    list[from].push_back(Edge((int)list[to].size(), from, to, cap));
    list[to].push_back(Edge((int)list[from].size() - 1, to, from, 0));
  }
};

// 最大流を求めるサブルーチンたち
static int level[MAX_V];
static int iter[MAX_V];

void dibfs(Graph &G, int s) {
  for (int i = 0; i < MAX_V; ++i) level[i] = -1;
  level[s] = 0;
  queue<int> que;
  que.push(s);
  while (!que.empty()) {
    int v = que.front();
    que.pop();
    for (int i = 0; i < G[v].size(); ++i) {
      Edge &e = G[v][i];
      if (level[e.to] < 0 && e.cap > 0) {
        level[e.to] = level[v] + 1;
        que.push(e.to);
      }
    }
  }
}

FLOW didfs(Graph &G, int v, int t, FLOW f) {
  if (v == t) return f;
  for (int &i = iter[v]; i < G[v].size(); ++i) {
    Edge &e = G[v][i], &re = G.redge(e);
    if (level[v] < level[e.to] && e.cap > 0) {
      FLOW d = didfs(G, e.to, t, min(f, e.cap));
      if (d > 0) {
        e.cap -= d;
        re.cap += d;
        return d;
      }
    }
  }
  return 0;
}

// 最大流を求めるメイン関数
FLOW Dinic(Graph &G, int s, int t) {
  FLOW res = 0;
  while (true) {
    dibfs(G, s);
    if (level[t] < 0) return res;
    memset(iter, 0, sizeof(iter));
    FLOW flow;
    while ((flow = didfs(G, s, t, FLOW_INF)) > 0) {
      res += flow;
    }
  }
}


// ----- library end ----- //



int main() {

  int N = ii();
  vector<int> As;
  vector<int> Bs;
  vector<int> Cs;
  vector<int> Ds;

  rep(i, N){
    As.PB(ii());
    Bs.PB(ii());
  }
  rep(i, N){
    Cs.PB(ii());
    Ds.PB(ii());
  }


  //最大二部マッチングをネットワークフロー問題（最大流問題）に帰着させて解く

  //グラフの定義（S, Tを合わせたノード数を引数に）
  int G1num = N; //グループ1のノード数
  int G2num = N; //グループ2のノード数
  Graph graph(G1num + G2num + 2);

  //追加するS, Tのindexを決める
  //グラフデータの最後2つに入れるようにすればいい
  int S_idx = G1num + G2num;
  int T_idx = G1num + G2num + 1;

  //グラフに枝を張る

  //まず、Sから全てのG1ノードへ、全てのG2ノードからTへと枝を張る
  //3つ目の引数は枝ごとの最大容量であり、最大二部マッチングでは常に1とする
  rep(g1_idx, G1num){
    graph.addedge(S_idx, g1_idx, 1);
  }
  rep(g2_idx, G2num){
    //G2のidxはG1のノード数だけ後ろにズレるので注意
    graph.addedge(G1num + g2_idx, T_idx, 1);
  }

  //G1の各ノードとG2の各ノードのうち、マッチングできるところに枝を張る
  //今回は、x,y座標ともにg1<g2となることがマッチングの条件
  rep(g1_idx, G1num){
    rep(g2_idx, G2num){
      if (As[g1_idx] < Cs[g2_idx] && Bs[g1_idx] < Ds[g2_idx]){
        //G2のidxはG1のノード数だけ後ろにズレるので注意
        graph.addedge(g1_idx, G1num + g2_idx, 1);
      }
    }
  }

  //最大流を求める
  FLOW ans = Dinic(graph, S_idx, T_idx);

  oi(ans);



// -- main() end -- //
}


Measured memory_bytes: 384