#include <algorithm>
#include <cassert>
#include <iostream>
#include <vector>
using namespace std;

const int INF = 1e9 + 5;

struct dinic_matching {
    int N = -1, M = -1;
    vector<vector<int>> adj;
    vector<int> dist;
    vector<int> prev;
    vector<int> q;
    int q_start, q_end;
    vector<bool> matched;
    vector<int> edge_index;

    dinic_matching(int n = -1, int m = -1) {
        if (n >= 0 && m >= 0)
            init(n, m);
    }

    void init(int n, int m) {
        N = n;
        M = m;

        adj.assign(N, {});
        dist.resize(N);
        q.resize(N);
        matched.resize(N);
        edge_index.resize(N);

        prev.resize(M);
    }

    void add_edge(int a, int b) {
        assert(0 <= a && a < N);
        assert(0 <= b && b < M);
        adj[a].push_back(b);
    }

    void bfs_check(int num, int potential_dist) {
        if (potential_dist < dist[num]) {
            dist[num] = potential_dist;
            q[q_end++] = num;
        }
    }

    bool bfs() {
        fill(dist.begin(), dist.end(), INF);
        q_start = q_end = 0;

        for (int i = 0; i < N; i++)
            if (!matched[i])
                bfs_check(i, 0);

        bool has_path = false;

        while (q_start < q_end) {
            int num = q[q_start++];

            for (int next : adj[num])
                if (prev[next] < 0)
                    has_path = true;
                else
                    bfs_check(prev[next], dist[num] + 1);
        }

        return has_path;
    }

    bool dfs(int num) {
        // Because we are only performing DFS in increasing order of dist, we don't have to revisit fully searched edges
        // again later.
        while (edge_index[num] < (int) adj[num].size()) {
            int next = adj[num][edge_index[num]];
            bool solved = false;

            if (prev[next] < 0 || (dist[num] + 1 == dist[prev[next]] && dfs(prev[next]))) {
                prev[next] = num;
                matched[num] = true;
                solved = true;
            }

            edge_index[num]++;

            if (solved)
                return true;
        }

        dist[num] = INF;
        return false;
    }

    int match() {
        assert(N >= 0 && M >= 0);

        for (int i = 0; i < N; i++)
            matched[i] = false;

        for (int j = 0; j < M; j++)
            prev[j] = -1;

        int matches = 0;

        while (bfs()) {
            for (int i = 0; i < N; i++)
                edge_index[i] = 0;

            for (int i = 0; i < N; i++)
                if (!matched[i] && dfs(i))
                    matches++;
        }

        return matches;
    }

    vector<bool> reachable_left, reachable_right;

    void reachable_dfs(int left) {
        reachable_left[left] = true;

        for (int right : adj[left])
            if (prev[right] != left && !reachable_right[right]) {
                reachable_right[right] = true;
                int next_left = prev[right];

                if (next_left >= 0 && !reachable_left[next_left])
                    reachable_dfs(next_left);
            }
    }

    // The min vertex cover in a bipartite graph corresponds to the min cut in its flow graph.
    vector<int> min_vertex_cover() {
        int size = match();
        reachable_left.assign(N, false);
        reachable_right.assign(M, false);

        for (int i = 0; i < N; i++)
            if (!matched[i] && !reachable_left[i])
                reachable_dfs(i);

        vector<int> cover;

        for (int i = 0; i < N; i++)
            if (!reachable_left[i])
                cover.push_back(i);

        for (int i = 0; i < M; i++)
            if (reachable_right[i])
                cover.push_back(N + i);

        assert((int) cover.size() == size);
        return cover;
    }

    // The max independent set is the complement of the min vertex cover.
    vector<int> max_independent_set() {
        int cover_size = min_vertex_cover().size();
        vector<int> independent_set;

        for (int i = 0; i < N; i++)
            if (reachable_left[i])
                independent_set.push_back(i);

        for (int i = 0; i < M; i++)
            if (!reachable_right[i])
                independent_set.push_back(N + i);

        assert((int) independent_set.size() == N + M - cover_size);
        return independent_set;
    }
};


int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int N;
    cin >> N;
    vector<int> A(N), B(N), C(N), D(N);

    for (int i = 0; i < N; i++)
        cin >> A[i] >> B[i];

    for (int i = 0; i < N; i++)
        cin >> C[i] >> D[i];

    dinic_matching graph(N, N);

    for (int i = 0; i < N; i++)
        for (int j = 0; j < N; j++)
            if (A[i] < C[j] && B[i] < D[j])
                graph.add_edge(i ,j);

    cout << graph.match() << '\n';
}


Measured memory_bytes: 256