#include <iostream>
#include <vector>
#include <algorithm>
#include <tuple>
#include <queue>

using namespace std;
using ll = long long;
template<class T> using V = vector<T>;
template<class T> using VV = V<V<T>>;
constexpr ll TEN(int n) { return (n==0 ? 1 : 10 * TEN(n-1)); }

template<class C>
struct MaxFlow {
    C flow;
    V<bool> dual;
};

template<class C, C EPS, class E>
struct MF_EXEC {
    static const C INF = numeric_limits<C>::max();
    VV<E> &g;
    int s, t;
    int N;
    V<int> level, iter;

    MaxFlow<C> info;
    MF_EXEC(VV<E> &g, int s, int t) : g(g), s(s), t(t) {
        N = int(g.size());
        level = V<int>(N);
        iter = V<int>(N);

        C flow = 0;
        while (true) {
            bfs();
            if (level[t] < 0) break;
            fill(begin(iter), end(iter), 0);
            while (true) {
                C f = dfs(s, INF);
                if (!f) break;
                flow += f;
            }
        }
        V<bool> dual(N);
        for (int i = 0; i < N; i++) {
            dual[i] = level[i] == -1;
        }
        info = MaxFlow<C>{flow, dual};
    }

    void bfs() {
        queue<int> que;
        fill(begin(level), end(level), -1);
        level[s] = 0;
        que.push(s);
        while (!que.empty()) {
            int v = que.front(); que.pop();
            for (E e: g[v]) {
                if (e.cap <= EPS) continue;
                if (level[e.to] < 0) {
                    level[e.to] = level[v] + 1;
                    que.push(e.to);
                }
            }
        }
    }

    C dfs(int v, C f) {
        if (v == t) return f;
        for (int &i = iter[v]; i < int(g[v].size()); i++) {
            E &e = g[v][i];
            if (e.cap <= EPS) continue;
            if (level[v] < level[e.to]) {
                C d = dfs(e.to, min(f, e.cap));
                if (d <= EPS) continue;
                e.cap -= d;
                g[e.to][e.rev].cap += d;
                return d;
            }
        }
        return 0;
    }
};

template<class C, C EPS, class E>
MaxFlow<C> max_flow(VV<E> &g, int s, int t) {
    return MF_EXEC<C, EPS, E>(g, s, t).info;
}


struct E {
    int to, cap, rev;
};

const int T = 405;
int main() {
    int a, b;
    cin >> b >> a; b -= a;
    int v = a + b + T + T + 2;
    int sv = a + b + T + T, tv = sv + 1;

    VV<E> g(v);
    auto add_edge = [&](int from, int to, int cap) {
        g[from].push_back(E{to, cap, int(g[to].size())});
        g[to].push_back(E{from, 0, int(g[from].size())-1});
    };

    using P = pair<int, int>;
    VV<int> buf(T);

    for (int i = 0; i < a; i++) {
        add_edge(sv, i, 1);
        int s, t;
        cin >> s >> t;
        buf[s].push_back(t);
        for (int j = s; j <= t; j++) {
            add_edge(i, a + b + j, TEN(5));
        }
    }
    for (int i = 0; i < b; i++) {
        add_edge(a+i, tv, 1);
        int s, t;
        cin >> s >> t;
        buf[s].push_back(t);
        for (int j = s; j <= t; j++) {
            add_edge(a + b + T + j, a + i, TEN(5));
        }
    }
    for (int i = 0; i < T; i++) {
        add_edge(a + b + i, a + b + T + i, 1);
    }
    priority_queue<int, vector<int>, greater<int>> que;
    int cnt = 0;
    for (int i = 0; i < T; i++) {
        for (int d: buf[i]) {
            que.push(d);
        }
        while (que.size() && que.top() < i) que.pop();
        if (que.size() == 0) continue;
        cnt++;
        que.pop();
    }
    cout << cnt << endl;

    auto mf = max_flow<int, 0>(g, sv, tv);
    cout << mf.flow << endl;
    return 0;
}

Measured memory_bytes: 4684