#include <iostream>
#include <algorithm>
#include <string>
#include <vector>
#include <map>
#include <set>
#include <queue>
#include <cstdio>
#include <complex>
#include <numeric>
#include <string.h>
#include <random>
#define rep(i,n) for (int i = 0; i < (int)n; i++)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pi;
typedef pair<pi, pi> pp;
typedef pair<ll, ll> pl;
double PI = 3.141592653589793238462643383279;
const double EPS = 1e-9;
const ll MOD1 = 1000000007;
const ll MOD2 = 1000000021;
const int inf = 1 << 30;
const ll linf = 1LL << 60;

struct DINIC {

    struct edge {
        int to, cap, rev;

        edge () {}
        edge (int to_, int cap_, int rev_) : to(to_), cap(cap_), rev(rev_) {}
    };

    vector<vector<edge>> G;
    vector<int> level;
    vector<int> iter;

	void init (int n) {
        G.assign(n, vector<edge>());
        level.assign(n, 0);
        iter.assign(n, 0);
	}

    void add_edge(int from, int to, int cap){
        G[from].push_back(edge(to, cap, G[to].size()));
        G[to].push_back(edge(from, 0, G[from].size()-1));
    }

    void bfs(int s){
        fill(level.begin(), level.end(), -1);
        queue<int> que;
        level[s] = 0;
        que.push(s);
        while(!que.empty()){
            int v = que.front(); que.pop();
            for(int i = 0; i < (int)G[v].size(); i++){
                edge &e = G[v][i];
                if(e.cap > 0 && level[e.to] < 0){
                    level[e.to] = level[v]+1;
                    que.push(e.to);
                }
            }
        }
    }

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

    int max_flow(int s, int t){
        int flow = 0;
        for(;;){
            bfs(s);
            if(level[t] < 0) return flow;
            fill(iter.begin(), iter.end(), 0);
            int f;
            while((f = dfs(s, t, inf)) > 0){
                flow += f;
            }
        }
    }
};

struct UF {
    vector<int> par, rank, si;

    void init(int n){
        par.assign(n, 0);
        rank.assign(n, 0);
        si.assign(n, 1);
        iota(par.begin(), par.end(), 0);
    }

    int find(int x){
        if(par[x] == x) return x;
        else return par[x] = find(par[x]);
    }

    void unite(int x, int y){
        x = find(x);
        y = find(y);
        if(x == y) return;
        
        if(rank[x] < rank[y]){
            par[x] = y;
            si[y] += si[x];
        } else{
            par[y] = x;
            if(rank[x] == rank[y]) rank[x]++;
            si[x] += si[y];
        }
    }

    bool same(int x, int y){
        return find(x) == find(y);
    }
} uf;

ll N, M;
vector<int> row[1000], col[1000];
DINIC dinic;

int main() {
    cin >> N >> M;
    dinic.init(2*N+2);
    rep(i,M) {
        int x, y; cin >> x >> y;
        x--; y--;
        row[y].push_back(x);
        col[x].push_back(y);
    }
    uf.init(N);
    rep(i,N) {
        for (int r : row[i]) {
            for (int c : col[r]) {
                uf.unite(i,c);
            }
        }
    }
    int max_ans = 0;
    rep(i,N) {
        if (row[i].size() == 0) continue;
        if (i == uf.find(i)) {
            max_ans++;
        }
    }
    max_ans = M-max_ans;
    int min_ans = 0;
    rep(i,N) {
        for (int c : col[i]) {
            dinic.add_edge(i, N+c, 1);
        }
    }
    rep(i,N) {
        dinic.add_edge(2*N, i, 1);
        dinic.add_edge(N+i, 2*N+1, 1);
    }
    min_ans = M-dinic.max_flow(2*N, 2*N+1);
    cout << min_ans << " " << max_ans << endl;
}


Measured memory_bytes: 3400