// Original C++ solution (slower):
#include <bits/stdc++.h>

using namespace std;



typedef long long ll;

#define REP(i,n) for(int i=0,_n=(int)(n);i<_n;++i)

#define ALL(v) (v).begin(),(v).end()

#define CLR(t,v) memset(t,(v),sizeof(t))

template<class T1,class T2>ostream& operator<<(ostream& os,const pair<T1,T2>&a){return os<<"("<<a.first<<","<<a.second<< ")";}

template<class T>void pv(T a,T b){for(T i=a;i!=b;++i)cout<<(*i)<<" ";cout<<endl;}

template<class T>void chmin(T&a,const T&b){if(a>b)a=b;}

template<class T>void chmax(T&a,const T&b){if(a<b)a=b;}





int nextInt() { int x; scanf("%d", &x); return x;}





const int NMAX = 1000;

const int MMAX = 1000;



int N,M;

bool edge[NMAX][MMAX];

int from[MMAX];

int visited[NMAX];



bool Add(int p) {

  if( visited[p] )return false;

  visited[p] = true;

  for(int i = 0; i < M; ++i) {

    if(!edge[p][i]) continue;

    if(from[i] < 0 || Add(from[i])) {

      from[i] = p;

      return true;

    }

  }

  return false;

}



int Matching() {

  memset(from,-1,sizeof(from));

  int cnt = 0;

  for(int i = 0; i < N; ++i) {

    memset(visited,0,sizeof(visited));

    if(Add(i))

      ++cnt;

  }

  return cnt;

}



int main2() {

  CLR(edge,0);

  CLR(edge,0);



  vector<int> a, b, c, d;

  N = M = nextInt();

  REP(i, N) {

    a.push_back(nextInt());

    b.push_back(nextInt());

  }

  REP(i, N) {

    c.push_back(nextInt());

    d.push_back(nextInt());

  }

  REP(i, N) {

    REP(j, N) {

      if (a[i] < c[j] && b[i] < d[j]) {

        edge[i][j] = true;

      }

    }

  }

  ll ans = Matching();

  cout << ans << endl;

  return 0;

}



int main() {

  for (;!cin.eof();cin>>ws)

    main2();

  return 0;

}


// Optimized solution to the same problem (faster):
#include <bits/stdc++.h>

using namespace std;



typedef long long ll;

#define REP(i,n) for(int i=0,_n=(int)(n);i<_n;++i)

#define ALL(v) (v).begin(),(v).end()

#define CLR(t,v) memset(t,(v),sizeof(t))

template<class T1,class T2>ostream& operator<<(ostream& os,const pair<T1,T2>&a){return os<<"("<<a.first<<","<<a.second<< ")";}

template<class T>void pv(T a,T b){for(T i=a;i!=b;++i)cout<<(*i)<<" ";cout<<endl;}

template<class T>void chmin(T&a,const T&b){if(a>b)a=b;}

template<class T>void chmax(T&a,const T&b){if(a<b)a=b;}



int nextInt() { int x; scanf("%d", &x); return x;}

ll nextLong() { ll x; scanf("%lld", &x); return x;}



struct P {

  int color, x, y;

};



const int MAX_N = 105;

P p[2 * MAX_N];



int solve(int N) {

  sort(p, p+N, [](const P&a, const P&b){

    if (a.x != b.x) return a.x < b.x;

    if (a.color != b.color) return a.color < b.color;

    return a.y < b.y;

  });



  int ans = 0;

  multiset<int> st;

  REP(i, N) {

    if (p[i].color == 0) {

      st.insert(p[i].y);

    }

    if (p[i].color == 1) {

      auto it = st.upper_bound(p[i].y);

      if (it != st.begin()) {

        --it;

        if ((*it) <= p[i].y) {

          st.erase(it);

          ans++;

        }

      }

    }

  }

  return ans;

}



int main2() {

  int N = nextInt();

  REP(i, N) {

    int a = nextInt();

    int b = nextInt();

    p[i] = (P){0, a, b};

  }

  REP(i, N) {

    int c = nextInt();

    int d = nextInt();

    p[N+i] = (P){1, c, d};

  }

  int ans = solve(2*N);

  cout << ans << endl;

  return 0;

}



int main() {



#ifdef LOCAL

  for (;!cin.eof();cin>>ws)

#endif

    main2();

  return 0;

}


// Fastest known solution for this problem: