#include <bits/stdc++.h>
// #include <atcoder/maxflow> // #
using namespace std;
#define all(CONT) begin(CONT), end(CONT)
template <typename T> void CERR(const T &ELEM) { cerr << ELEM; }
#define dbl(OBJ) cerr << #OBJ << ": "; CERR(OBJ); cerr << "\n"
template <typename T, typename ...Ts> void CERR(const T &FIRST, const Ts &...REST) { CERR(FIRST); cerr << ", "; CERR(REST...); }
#define dbs(...) cerr << "(" << #__VA_ARGS__ << "): ("; CERR(__VA_ARGS__); cerr << ")\n"
#define itrep(ITR, CONT) for (auto ITR = begin(CONT); ITR != end(CONT); ITR++)
template <typename T> void CERR(const vector<T> &VEC) { cerr << "{ "; itrep(ITR, VEC) { CERR(*ITR); cerr << ", "; } cerr << "}"; }
// MaxFlow
namespace atcoder {

namespace internal {

template <class T> struct simple_queue {
    std::vector<T> payload;
    int pos = 0;
    void reserve(int n) { payload.reserve(n); }
    int size() const { return int(payload.size()) - pos; }
    bool empty() const { return pos == int(payload.size()); }
    void push(const T& t) { payload.push_back(t); }
    T& front() { return payload[pos]; }
    void clear() {
        payload.clear();
        pos = 0;
    }
    void pop() { pos++; }
};

}  // namespace internal

template <class Cap> struct mf_graph {
  public:
    mf_graph() : _n(0) {}
    mf_graph(int n) : _n(n), g(n) {}

    int add_edge(int from, int to, Cap cap) {
        assert(0 <= from && from < _n);
        assert(0 <= to && to < _n);
        assert(0 <= cap);
        int m = int(pos.size());
        pos.push_back({from, int(g[from].size())});
        g[from].push_back(_edge{to, int(g[to].size()), cap});
        g[to].push_back(_edge{from, int(g[from].size()) - 1, 0});
        return m;
    }

    struct edge {
        int from, to;
        Cap cap, flow;
    };

    edge get_edge(int i) {
        int m = int(pos.size());
        assert(0 <= i && i < m);
        auto _e = g[pos[i].first][pos[i].second];
        auto _re = g[_e.to][_e.rev];
        return edge{pos[i].first, _e.to, _e.cap + _re.cap, _re.cap};
    }
    std::vector<edge> edges() {
        int m = int(pos.size());
        std::vector<edge> result;
        for (int i = 0; i < m; i++) {
            result.push_back(get_edge(i));
        }
        return result;
    }
    void change_edge(int i, Cap new_cap, Cap new_flow) {
        int m = int(pos.size());
        assert(0 <= i && i < m);
        assert(0 <= new_flow && new_flow <= new_cap);
        auto& _e = g[pos[i].first][pos[i].second];
        auto& _re = g[_e.to][_e.rev];
        _e.cap = new_cap - new_flow;
        _re.cap = new_flow;
    }

    Cap flow(int s, int t) {
        return flow(s, t, std::numeric_limits<Cap>::max());
    }
    Cap flow(int s, int t, Cap flow_limit) {
        assert(0 <= s && s < _n);
        assert(0 <= t && t < _n);

        std::vector<int> level(_n), iter(_n);
        internal::simple_queue<int> que;

        auto bfs = [&]() {
            std::fill(level.begin(), level.end(), -1);
            level[s] = 0;
            que.clear();
            que.push(s);
            while (!que.empty()) {
                int v = que.front();
                que.pop();
                for (auto e : g[v]) {
                    if (e.cap == 0 || level[e.to] >= 0) continue;
                    level[e.to] = level[v] + 1;
                    if (e.to == t) return;
                    que.push(e.to);
                }
            }
        };
        auto dfs = [&](auto self, int v, Cap up) {
            if (v == s) return up;
            Cap res = 0;
            int level_v = level[v];
            for (int& i = iter[v]; i < int(g[v].size()); i++) {
                _edge& e = g[v][i];
                if (level_v <= level[e.to] || g[e.to][e.rev].cap == 0) continue;
                Cap d =
                    self(self, e.to, std::min(up - res, g[e.to][e.rev].cap));
                if (d <= 0) continue;
                g[v][i].cap += d;
                g[e.to][e.rev].cap -= d;
                res += d;
                if (res == up) break;
            }
            return res;
        };

        Cap flow = 0;
        while (flow < flow_limit) {
            bfs();
            if (level[t] == -1) break;
            std::fill(iter.begin(), iter.end(), 0);
            while (flow < flow_limit) {
                Cap f = dfs(dfs, t, flow_limit - flow);
                if (!f) break;
                flow += f;
            }
        }
        return flow;
    }

    std::vector<bool> min_cut(int s) {
        std::vector<bool> visited(_n);
        internal::simple_queue<int> que;
        que.push(s);
        while (!que.empty()) {
            int p = que.front();
            que.pop();
            visited[p] = true;
            for (auto e : g[p]) {
                if (e.cap && !visited[e.to]) {
                    visited[e.to] = true;
                    que.push(e.to);
                }
            }
        }
        return visited;
    }

  private:
    int _n;
    struct _edge {
        int to, rev;
        Cap cap;
    };
    std::vector<std::pair<int, int>> pos;
    std::vector<std::vector<_edge>> g;
};

}  // namespace atcoder

using namespace std;
using namespace atcoder; // #
#define rep(CNT, GOAL) for (int CNT = 0; CNT < (int)(GOAL); CNT++)
#define rep2(CNT, START, GOAL) for (int CNT = (int)(START); CNT < (int)(GOAL); CNT++)

struct Graph{
	int V; // 0-ind
	vector<vector<int>> adj;
	vector<bool> r; // reached
	vector<int> dis; // struct内に置く
	Graph(int _V) {
		V = _V;
		adj = vector<vector<int>>(V);
		r = vector<bool>(V);
		dis = vector<int>(V);
	}
	void add_edge(int u, int v) { // u-v (indirected)
		adj[u].push_back(v);
		adj[v].push_back(u);
	}
	// 連結成分ごとに，番号の最も若い頂点からの距離を求めるBFS(struct内に置く)
	void bfs_dis(int s) {
		dis[s] = 0; // 値と到達フラグを分ける
		queue<int> q; q.push(s); r[s] = true;
		while (!q.empty()) {
			int u = q.front(); q.pop();
			for (int v : adj[u]) {
				if (r[v]) continue;
				q.push(v); r[v] = true;
				dis[v] = dis[u] + 1;
			}
		}
	}
	void bfs_dis() {
		r = vector<bool>(V, false); // 到達フラグ初期化
		dis = vector<int>(V, -1); // 距離初期化
		for (int s = 0; s < V; s++) {
			if (r[s]) continue;
			bfs_dis(s); // sを始点としてBFS
		}
	}
	bool is_bipartite(int s) {
		queue<int> q; q.push(s); r[s] = true;
		while (!q.empty()) {
			int u = q.front(); q.pop();
			for (int v : adj[u]) {
				if ((dis[v] - dis[u]) % 2 == 0) return false;
				if (!r[v]) {
					q.push(v); r[v] = true;
				}
			}
		}
		return true;
	}
	bool is_bipartite() {
		bfs_dis();
		r = vector<bool>(V, false);
		for (int s = 0; s < V; s++) {
			if (r[s]) continue;
			if (!is_bipartite(s)) return false;
		}
		return true;
	}
	// 色分け // 非連結な場合，色の組合せ方が複数あることに注意
	vector<vector<int>> parts() {
		assert(is_bipartite());
		vector<vector<int>> p(2);
		for (int i = 0; i < V; i++) {
			p[dis[i] % 2].push_back(i);
		}
		return p;
	}
	void show() {
		for (int i = 0; i < V; i++) {
			cout << i << ": { ";
			for (int j : adj[i]) cout << j << ", ";
			cout << "}" << endl;
		}
	}
	template <typename T> // #defineに移す
	void show(vector<T> &val) {
		cout << "{ ";
		for (int i = 0; i < val.size(); i++) cout << val[i] << ", ";
		cout << "}" << endl;
	}
};

// 頂点集合のペアp={pre,post}を引数とする -> parts
int bip_max_matching(Graph &graph, vector<vector<int>> &p) {
	assert(p.size() == 2);
	int N1 = p[0].size(), N2 = p[1].size();
	assert(N1 + N2 == graph.V);
	mf_graph<int> g(N1 + N2 + 2);
	for (int u : p[0]) g.add_edge(N1 + N2, u, 1);
	for (int u : p[0]) for (int v : graph.adj[u]) g.add_edge(u, v, 1);
	for (int v : p[1]) g.add_edge(v, N1 + N2 + 1, 1);
	return g.flow(N1 + N2, N1 + N2 + 1);
}
// graphは前半と後半に分かれた二部グラフ
// p={{0..N1-1},{N1,N1+N2-1}}に対し
int bip_max_matching(Graph &graph, int N1, int N2) {
	vector<vector<int>> p = {vector<int>(N1), vector<int>(N2)};
	for (int i = 0; i < N1; i++) p[0][i] = i;
	for (int i = 0; i < N2; i++) p[1][i] = N1 + i;
	return bip_max_matching(graph, p);
	// assert(N1 + N2 == graph.V);
	// // assert(graph.is_bipartite()); // 不十分
	// mf_graph<int> g(N1 + N2 + 2); // s=N1+N2->u in[0,N1)->v in[N1,N1+N2)->t=N1+N2+1
	// for (int u = 0; u < N1; u++) g.add_edge(N1 + N2, u, 1);
	// for (int u = 0; u < N1; u++) for (int v : graph.adj[u]) g.add_edge(u, v, 1);
	// for (int v = N1; v < N1 + N2; v++) g.add_edge(v, N1 + N2 + 1, 1);
	// return g.flow(N1 + N2, N1 + N2 + 1);
}


// bipの最大マッチングのほか，最大安定(独立)集合，最小辺カバー，最小点カバー

int main() {
	int N; cin >> N;
	vector<pair<int, int>> r(N), b(N);
	rep(i, N) cin >> r[i].first >> r[i].second;
	rep(i, N) cin >> b[i].first >> b[i].second;
	Graph g(2 * N);
	rep(i, N) rep(j, N) if (r[i].first < b[j].first and r[i].second < b[j].second) g.add_edge(i, N + j);
	cout << bip_max_matching(g, N, N) << endl;
}

Measured memory_bytes: 3780