//#include <boost/multiprecision/cpp_dec_float.hpp>
//#include <boost/multiprecision/cpp_ll.hpp>
#include <stdio.h>
#include <iostream>
#include <vector>
#include <string>
#include <set>
#include <map>
#include <queue>
#include <stack>
#include <math.h>
#include <algorithm>
#include <bitset>
#include <iomanip>
using namespace std;
//namespace mp = boost::multiprecision;
//using llll = mp::cpp_ll;
// 仮数部が1024ビットの浮動小数点数型(TLEしたら小さくする)
//using Real = mp::number<mp::cpp_dec_float<1024>>;
using ll = long long;
const double EPS = 1e-10;
const ll MOD = 7+(1e+9);

struct s_t{
  ll a,b;
};

template<typename T>
struct Dinic{
    struct edge
    {
        //行先と逆辺、revはid参照されているので、
        //追加された順に1,2,3,...となっている
        ll to,rev;
        //cap は capacityの略
        T cap;
        edge(ll to,T cap,ll rev):to(to),cap(cap),rev(rev){};
    };
    const T INF;
    //グラフと探索済みかの配列
    vector<vector<edge>> graph;
    vector<ll> iter,level;
    Dinic(ll n):INF(numeric_limits<T>::max()){
        graph.resize(n);
    }
    void add_edge(ll from, ll to, T cap){
        //from ~ toにcapの辺を通す
        graph[from].emplace_back(to,cap,ll(graph[to].size()));
        //to~from　に0容量の辺を通す
        graph[to].emplace_back(from,0,ll(graph[from].size())-1);
    }
    bool bfs(ll s, ll t){   //容量がある条件で最短距離の検索
        level.assign(graph.size(),-1);
        queue<ll> que;
        level[s] = 0;
        que.push(s);
        while(!que.empty() and level[t] == -1){
            ll p = que.front();
            que.pop();
            for(auto &e : graph[p]){
                if(e.cap > 0 and level[e.to] == -1){
                    que.push(e.to);
                    level[e.to] = level[p] + 1;
                }
            }
        }
        return level[t] != -1;
    }
    //ただのdfs探索
    //これは一本のパスについてflowを返していく
    T dfs(ll v, const ll t, T flow){
        //終了地点にきたらflowを返す
        if(v == t) return flow;
        for(ll &i = iter[v]; i < (ll)graph[v].size(); i++){
            //流す余地があって未探索
            auto &e = graph[v][i];
            if(e.cap > 0 and (level[v] < level[e.to])){
                //このノード以降のフローを求める
                T d = dfs(e.to,t,min(flow,e.cap));
                if(d > 0){
                    e.cap -= d;
                    graph[e.to][e.rev].cap += d;
                    return d;
                }
            }
        }
        return 0; //ここにたどり着くのは増加パスがない時
    }
    T max_flow(ll s,ll t){
        T flow = 0;
        //cout << " bfs?" << " " << bfs(s,t) << endl;
        while(bfs(s,t)){
            iter.assign(graph.size(),0);  //探索回数のリセット
            T f = 0;
            while((f = dfs(s,t,INF)) > 0){  //dfsで見つかる限りflowを追加
                flow += f;
            }
        }
        return flow;
    }
};

int main(){
  ll ans=0;
  ll n;
  cin>>n;
  Dinic<ll> D(n*2+2);
  s_t z;
  vector<s_t>a,b;
  for(ll i=0;i<n;i++){
    cin>>z.a>>z.b;
    a.push_back(z);
  }
  for(ll i=0;i<n;i++){
    cin>>z.a>>z.b;
    b.push_back(z);
  }
  for(ll i=0;i<n;i++){
    for(ll j=0;j<n;j++){
      if((a[i].a<b[j].a)&&(a[i].b<b[j].b)){
        D.add_edge(1+i,1+n+j,1);
      }
    }
  }
  for(ll i=0;i<n;i++){
    D.add_edge(0,1+i,1);
  }
  for(ll i=0;i<n;i++){
    D.add_edge(1+n+i,n*2+1,1);
  }
  ans=D.max_flow(0,n*2+1);
  cout<<ans<<endl;
}


Measured memory_bytes: 888