#include <bits/stdc++.h>

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

#define ALL(c)   c.begin(), c.end()
#define SORT(c)  std::sort(ALL(c))
#define RSORT(c) std::sort(ALL(c), std::greater<decltype(c)::value_type>())

using namespace std;
using ll = long long;

const int MOD  = (int)1e9 + 7;
const int INF  = (int)1e9 + 1;
const ll  LINF = (long long)1e18 + 1;

class Graph {
public:
    struct Edge {
        int       src, dst;
        long long cost, capacity, flow;
        size_t    rev;
        Edge(const int&       _src,
             const int&       _dst,
             const long long& _cost     = 0,
             const long long& _capacity = 0,
             const long long& _frow     = 0,
             const size_t&    _rev      = 0) :
            src(_src),
            dst(_dst),
            cost(_cost),
            capacity(_capacity),
            flow(_frow),
            rev(_rev) { }
    };

    struct Vertex {
        long long cost;
        Vertex(const long long& _cost) :
            cost(_cost) { }
    };

    enum class Type {
        Normal,
        Flow,
    };

    enum class EdgeType {
        Directed,
        Undirected,
    };

    std::vector<Vertex>            vertex;
    std::vector<std::vector<Edge>> edge;

    Graph(const int&      num_vertex,
          const Type&     type,
          const EdgeType& edge_type) :
        vertex(num_vertex, 0),
        edge(num_vertex),
        type_(type),
        edge_type_(edge_type) { }

    void addEdge(const int&        src,
                 const int&        dst,
                 const long long&  cost,
                 const long long&  capacity = 0) {
        switch(type_) {
            case Type::Normal: {
                switch(edge_type_) {
                    case EdgeType::Directed: {
                        edge[src].emplace_back(src, dst, cost);
                        break;
                    }
                    case EdgeType::Undirected: {
                        edge[src].emplace_back(src, dst, cost);
                        edge[dst].emplace_back(dst, src, cost);
                        break;
                    }
                }
            }
            case Type::Flow: {
                switch(edge_type_) {
                    case EdgeType::Directed: {
                        edge[src].emplace_back(src, dst, cost,  capacity, 0, edge[dst].size());
                        edge[dst].emplace_back(dst, src, -cost,        0, 0, edge[src].size() - 1);
                        break;
                    }
                    case EdgeType::Undirected: {
                        edge[src].emplace_back(src, dst, cost, capacity, 0, edge[dst].size());
                        edge[dst].emplace_back(dst, src, cost, capacity, 0, edge[src].size() - 1);
                        break;
                    }
                }
            }
        }
    }

    // 最大流問題を解く
    // Dinic's blocking flow
    long long calcMaxFlow(const int& src_index,
                          const int& sink_index) {
        if(type_ != Type::Flow) {
            throw std::logic_error("Graph type is invalid");
        }
        std::vector<int> level(vertex.size()), iter(vertex.size());
        std::function<int(void)> levelize
            = [&]() {
                  level.assign(vertex.size(), -1);
                  level[src_index] = 0;
                  queue<int> Q;
                  Q.push(src_index);
                  while(!Q.empty()) {
                      int u = Q.front();
                      Q.pop();
                      if(u == sink_index) {
                          break;
                      }
                      for(const auto& e : edge[u]) {
                          if(e.flow < e.capacity && level[e.dst] < 0) {
                              Q.push(e.dst);
                              level[e.dst] = level[u] + 1;
                          }
                      }
                  }
                  return level[sink_index];
              };
        function<long long(int, long long)> augment
            = [&](int u, long long cur) {
                  if(u == sink_index) {
                      return cur;
                  }
                  for(size_t i = iter[u]; i < edge[u].size(); ++i) {
                      auto& e = edge[u][i];
                      auto& r = edge[e.dst][e.rev];
                      if(e.flow < e.capacity && level[u] < level[e.dst]) {
                          auto f = augment(e.dst, min(cur, e.capacity - e.flow));
                          if(0 < f) {
                              e.flow += f;
                              r.flow -= f;
                              return f;
                          }
                      }
                  }
                  return 0LL;
              };
        for(auto& v : edge) {
            for(auto &e : v) {
                e.flow = 0;
            }
        }
        auto flow = 0LL;
        while(0 <= levelize()) {
            std::fill(iter.begin(), iter.end(), 0);
            for(long long f; 0 < (f = augment(src_index, INF)); flow += f);
        }
        return flow;
    }

    // 幅優先探索で最小コストを求める
    bool calcMinCostByBFS(long long& min_cost,
                          const int& start_index,
                          const int& goal_index) {
        if(type_ != Type::Normal) {
            throw std::logic_error("Graph type is invalid");
        }
        std::fill(vertex.begin(), vertex.end(), std::numeric_limits<long long>::max());
        vertex[start_index] = 0;
        queue<int> queue;
        queue.push(start_index);
        while(!queue.empty()) {
            const auto src = queue.front();
            queue.pop();
            for(const auto& v : edge[src]) {
                const auto& dst  = v.dst;
                const auto  cost = vertex[src].cost + v.cost;
                if(cost < vertex[dst].cost) {
                    vertex[dst] = cost;
                    queue.push(dst);
                }
            }
        }
        min_cost = vertex[goal_index].cost;
        return (min_cost == std::numeric_limits<long long>::max()) ? false : true;
    }

private:
    const Type     type_;
    const EdgeType edge_type_;
};

class Pt {
public:
    int x;
    int y;

    constexpr Pt() : x(0), y(0) {}
    constexpr Pt(const int& _x, const int& _y) : x(_x), y(_y) {}

    constexpr Pt(const Pt &obj) : x(obj.x), y(obj.y) {}
    Pt &operator=(const Pt &obj) { x = obj.x; y = obj.y; return *this; }

    std::vector<Pt> getAroundFourPts() const {
        std::vector<Pt> result;
        for(int hwi = -1; hwi <= 1; hwi++) {
            for(int wwi = -1; wwi <= 1; wwi++) {
                if((hwi == 0) ^ (wwi == 0)) {
                    result.emplace_back(x + wwi, y + hwi);
                }
            }
        }
        return result;
    }

    std::vector<Pt> getAroundPts() const {
        std::vector<Pt> result;
        for(int hwi = -1; hwi <= 1; hwi++) {
            for(int wwi = -1; wwi <= 1; wwi++) {
                if(hwi != 0 || wwi != 0) {
                    result.emplace_back(x + wwi, y + hwi);
                }
            }
        }
        return result;
    }

    constexpr bool isWithinRange(const int& w, const int& h) const {
        return 0 <= x && x < w && 0 <= y && y < h;
    }

    constexpr Pt operator +() const {
        return *this;
    }

    constexpr Pt operator -() const {
        return{ -x, -y };
    }

    constexpr Pt operator +(const Pt& other) const {
        return{ x + other.x, y + other.y };
    }

    constexpr Pt operator +=(const Pt& other) {
        x += other.x;  y += other.y;  return *this;
    }

    constexpr Pt operator -(const Pt& other) const {
        return{ x - other.x, y - other.y };
    }

    constexpr Pt operator -=(const Pt& other) {
        x -= other.x;  y -= other.y;  return *this;
    }

    constexpr Pt operator *(int s) const {
        return{ x * s, y * s };
    }

    constexpr Pt operator *=(const Pt& other) {
        x *= other.x;  y *= other.y;  return *this;
    }

    constexpr Pt operator /(int s) const {
        return{ x / s, y / s };
    }

    constexpr Pt operator /=(const Pt& other) {
        x /= other.x;  y /= other.y;  return *this;
    }

    constexpr bool operator ==(const Pt& other) {
        return x == other.x && y == other.y;
    }

    constexpr bool operator !=(const Pt& other) {
        return !(x == other.x && y == other.y);
    }

    friend std::ostream& operator <<(std::ostream& os, const Pt& obj) {
        os << "x : " << obj.x << ", "
           << "y : " << obj.y << std::endl;
        return os;
    }
};

template <typename T>
class MyMat {
public:
    using EleMap = std::map<T, std::vector<Pt>>;

    T* data;
    uint w, h;

    MyMat(const uint& _w, const uint& _h, const T& def_val = 0) :
        w(_w), h(_h) {
        data = new T[h * w]();
        if(def_val != 0) {
            for(uint i = 0; i < w * h; i++) {
                data[i] = def_val;
            }
        }
    }

    MyMat(const uint &_w, const uint &_h, const T*& _data) :
        w(_w), h(_h), data(_data) {}

    ~MyMat() { delete[] data; }

    MyMat(const MyMat &obj) : data(obj.data), w(obj.w), h(obj,h) {}
    MyMat &operator=(const MyMat &obj) { data = obj.data; w = obj.w; h = obj,h; return *this; }

    MyMat clone() {
        T* new_data = new T[h * w];
        for(int i = 0; i < h * w; i++) {
            new_data[i] = data[i];
        }

        return MyMat<T>(w, h, new_data);
    }

    void stdRead() {
        for(uint hi = 0; hi < h; hi++) {
            for(uint wi = 0; wi < w; wi++) {
                std::cin >> data[wi + hi * w];
            }
        }
    }

    template<class... A>
    EleMap searchElements(const A&... keys) const {
        auto key_list = std::initializer_list<T>{keys...};

        EleMap result;
        for(const auto& key : key_list) {
            result.insert(std::make_pair(key, std::vector<Pt>()));
        }

        for(uint hi = 0; hi < h; hi++) {
            for(uint wi = 0; wi < w; wi++) {
                for(const auto& key : key_list) {
                    if(data[wi + hi * w] == key) {
                        result[key].push_back(Pt(wi, hi));
                    }
                }
            }
        }
    }

    T& at(const uint& hi, const uint& wi) const {
        return data[wi + hi * w];
    }

    T& at(const Pt& pt) const {
        return data[pt.x + pt.y * w];
    }

    friend std::ostream& operator <<(std::ostream& os, const MyMat<T>& obj) {
        for(uint hi = 0; hi < obj.h; hi++) {
            for (uint wi = 0; wi < obj.w; wi++) {
                os << obj.data[wi + hi * obj.w] << " ";
            }
            os << std::endl;
        }
        return os;
    }
};

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

    int N;
    std::cin >> N;

    vector<Pt> red(N), blue(N);
    REP(i, N) {
        std::cin >> red[i].x >> red[i].y;
    }
    REP(i, N) {
        std::cin >> blue[i].x >> blue[i].y;
    }

    Graph g(N * 2 + 2, Graph::Type::Flow, Graph::EdgeType::Directed);
    const auto S = 0;
    const auto T = N * 2 + 1;
    REP(i, N) {
        g.addEdge(S, i + 1, 0, 1);
    }
    REP(i, N) {
        g.addEdge(i + N + 1, T, 0, 1);
    }
    REP(i, N) {
        REP(j, N) {
            if(red[i].x < blue[j].x && red[i].y < blue[j].y) {
                g.addEdge(i + 1, j + N + 1, 0, 1);
            }
        }
    }

    std::cout << g.calcMaxFlow(S, T) << std::endl;

    return 0;
}


Measured memory_bytes: 640