#include <bits/stdc++.h>
using namespace std;

#define TRACE(x) x
#define WATCH(x) TRACE(cout << #x" = " << x << endl)
#define WATCHR(a, b) TRACE(for (auto it=a; it!=b;) cout << *(it++) << " "; cout << endl)
#define WATCHC(V) TRACE({cout << #V" = "; WATCHR(V.begin(), V.end());})

#define sz(x) int((x).size())
#define all(x) (x).begin(), (x).end()

using ll = long long;
using vi = vector<int>;
using vvi = vector<vi>;
using vll = vector<ll>;
using vvll = vector<vll>;
using vb = vector<bool>;
using vs = vector<string>;

mt19937_64 rng(chrono::steady_clock::now().time_since_epoch().count());

template<typename F> struct dinic {
    static const F inf = numeric_limits<F>::max();

    int V;
    vvi adj;
    vi dest;
    vector<F> cap;

    dinic (int V = 0) : V(V) {
        adj.resize(V);
    }

    void __arc(int u, int v, F c) {
        adj[u].push_back(dest.size());
        dest.push_back(v);
        cap.push_back(c);
    }

    // Inserts a directed edge u --> v with capacity c.
    void arc(int u, int v, F c) {
        __arc(u, v, c);
        __arc(v, u, F(0));
    }

    bool bfs(int s, int t, vi& level, vector<F>& flow) const {
        level = vi(V, -1);
        level[s] = 0;
        for (queue<int> q({s}); !q.empty(); q.pop()) {
            int u = q.front();
            for (int e : adj[u]) {
                if (level[dest[e]] == -1 && flow[e] < cap[e]) {
                    level[dest[e]] = level[u] + 1;
                    q.push(dest[e]);
                }
            }
        }
        return level[t] != -1;
    }

    F augment(int s, int t, vi& level, vector<F>& flow, vi& inx, F cur) const {
        if (s == t) return cur;
        for (int e; inx[s] < adj[s].size(); inx[s]++) {
            e = adj[s][inx[s]];
            if (level[dest[e]] != level[s] + 1) continue;
            if (flow[e] == cap[e]) continue;
            F incr = augment(dest[e], t, level, flow, inx, min(cur, cap[e] - flow[e]));
            if (incr > F(0)) {
                flow[e] += incr;
                flow[e^1] -= incr;
                return incr;
            }
        }
        return F(0);
    }

    /*
     * Computes a maximum flow from node s to node t.
     *
     * Runs in O(V^2 * E) in the general case.
     * Runs in O(min{ V^(2/3), E^(1/2) } * E) if all edges have unit capacity.
     * Runs in O(V^(1/2) * E) for bipartite matching.
     */
    tuple<F, vector<F>> max_flow(int s, int t) const {
        assert(s != t);
        F res(0);
        vector<F> flow(cap.size());
        for (vi level; bfs(s, t, level, flow); ) {
            for (vi inx(V, 0); F incr = augment(s, t, level, flow, inx, inf); )
                res += incr;
        }
        return make_tuple(res, flow);
    }
};

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);

    int N;
    cin >> N;

    dinic<int> g (2 * N + 2);
    const int SRC = 0, RED = 1, BLUE = 1 + N, SNK = 1 + 2 * N;

    vi rx(N), ry(N);
    for (int i = 0; i < N; i++) {
        cin >> rx[i] >> ry[i];
        g.arc(SRC, RED + i, 1);
    }

    vi bx(N), by(N);
    for (int i = 0; i < N; i++) {
        cin >> bx[i] >> by[i];
        g.arc(BLUE + i, SNK, 1);

        for (int j = 0; j < N; j++) {
            if (rx[j] < bx[i] && ry[j] < by[i])
                g.arc(RED + j, BLUE + i, 1);
        }
    }

    cout << get<0>(g.max_flow(SRC, SNK)) << endl;

    return 0;
}



Measured memory_bytes: 384