#pragma GCC optimize("unroll-loops")
#pragma GCC optimize("Ofast")
 
// hloya template v26
 
// ░░░░░░░▄▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▄░░░░░░
// ░░░░░░█░░▄▀▀▀▀▀▀▀▀▀▀▀▀▀▄░░█░░░░░
// ░░░░░░█░█░▀░░░░░▀░░▀░░░░█░█░░░░░
// ░░░░░░█░█░░░░░░░░▄▀▀▄░▀░█░█▄▀▀▄░
// █▀▀█▄░█░█░░▀░░░░░█░░░▀▄▄█▄▀░░░█░
// ▀▄▄░▀██░█▄░▀░░░▄▄▀░░░░░░░░░░░░▀▄
// ░░▀█▄▄█░█░░░░▄░░█░░░▄█░░░▄░▄█░░█
// ░░░░░▀█░▀▄▀░░░░░█░██░▄░░▄░░▄░███
// ░░░░░▄█▄░░▀▀▀▀▀▀▀▀▄░░▀▀▀▀▀▀▀░▄▀░
// ░░░░█░░▄█▀█▀▀█▀▀▀▀▀▀█▀▀█▀█▀▀█░░░
// ░░░░▀▀▀▀░░▀▀▀░░░░░░░░▀▀▀░░▀▀░░░░
 
#include <bits/stdc++.h>
using namespace std;
 
bool dbg = 0;
  
clock_t start_time = clock();
#define current_time fixed<<setprecision(6)<<(ld)(clock()-start_time)/CLOCKS_PER_SEC
  
#define f first
#define s second
#define mp make_pair
#define mt make_tuple
#define pb push_back
#define eb emplace_back
#define all(v) (v).begin(), (v).end()
#define sz(v) ((int)(v).size())
#define sqr(x) ((x) * (x))
 
#define ull unsigned long long
#define ll long long
#define ld long double
#define pii pair<int,int>
#define umap unordered_map<int, int>
 
#define files1 freopen("input.txt","r",stdin)
#define files2 freopen("output.txt","w",stdout)
#define files files1;files2
#define fast_io ios_base::sync_with_stdio(0);cin.tie(0)
 
// #define endl '\n'
#define ln(i,n) " \n"[(i) == (n) - 1]
 
void bad(string mes = "Impossible"){cout << mes;exit(0);}
void bad(int mes){cout << mes;exit(0);}
 
template<typename T>
string bin(T x, int st = 2){
    string ans = "";
    while (x > 0){
        ans += char('0' + x % st);
        x /= st;
    }
    reverse(ans.begin(), ans.end());
    return ans.empty() ? "0" : ans;
}
 
mt19937_64 mt_rand(
    chrono::system_clock::now().time_since_epoch().count()
);
 
template<typename T1, typename T2> inline bool upmax(T1& a, T2 b) { return (a < b ? (a = b, true) : false); }
template<typename T1, typename T2> inline bool upmin(T1& a, T2 b) { return (b < a ? (a = b, true) : false); }
 
// inline int popcount(int x){
//     int count = 0;
//     __asm__ volatile("POPCNT %1, %0;":"=r"(count):"r"(x):);
//     return count;
// }
  
template<typename T>
T input(){
    T ans = 0, m = 1;
    char c = ' ';
  
    while (!((c >= '0' && c <= '9') || c == '-')) {
        c = getchar();
    }
  
    if (c == '-')
        m = -1, c = getchar();
    while (c >= '0' && c <= '9'){
        ans = ans * 10 + (c - '0'), c = getchar();
    }
    return ans * m;
}
 
template<typename T>
T gcd (T a, T b) { while (b) { a %= b; swap (a, b); } return a; }
  
template<typename T> void read(T& a) { a = input<T>(); }
template<typename T> void read(T& a, T& b) { read(a), read(b); }
template<typename T> void read(T& a, T& b, T& c) { read(a, b), read(c); }
template<typename T> void read(T& a, T& b, T& c, T& d) { read(a, b), read(c, d); }
 
const int inf = 1e9 + 20;
const short short_inf = 3e4 + 20;
const long double eps = 1e-12;
const int maxn = (int)1e5 + 3, base = 998244353;
const ll llinf = 2e18 + 5;
 
int binpow(int a, int s) { 
    int res = 1;
    while (s) {
        if (s % 2) {
            res = 1ll * res * a % base;
        }
        a = 1ll * a * a % base;
        s /= 2;
    }
    return res;
}

struct Frac {
    ll p, q;
    Frac() {p = 0, q = 1;}
    Frac(ll p, ll q) : p(p), q(q) {}

    void norm() {
        if (q < 0) p = -p, q = -q;
        if (p == 0) q = 1;
        ll g = gcd(abs(p), q);
        p /= g;
        q /= g;
    }

    Frac& operator+=(const Frac& b) {
        p = p * b.q + q * b.p;
        q = q * b.q;
        norm();
        return *this;
    }

    friend Frac operator+(Frac a, const Frac& b) {
        a += b;
        return a;
    }

    Frac& operator*=(const Frac& b) {
        p = p * b.p;
        q = q * b.q;
        norm();
        return *this;
    }

    friend Frac operator*(Frac a, const Frac& b) {
        a *= b;
        return a;
    }

    Frac& operator-=(const Frac& b) {
        p = p * b.q - q * b.p;
        q = q * b.q;
        norm();
        return *this;                
    }

    friend Frac operator-(Frac a, const Frac& b) {
        a -= b;
        return a;
    }

    Frac& operator/=(const Frac& b) {
        p = p * b.q;
        q = q * b.p;
        norm();
        return *this;
    }

    friend Frac operator/(Frac a, const Frac& b) {
        a /= b;
        return a;
    }

    bool operator < (const Frac & T) const {
        return p * T.q < T.p * q;
    }

    bool operator <= (const Frac & T) const {
        return p * T.q <= T.p * q;
    }

    bool operator != (const Frac & T) const {
        return p * T.q != T.p * q;
    }
    
    bool operator == (const Frac & T) const {
        return p * T.q == T.p * q;
    }
};

std::ostream& operator<<(std::ostream& out, const Frac& f) {
   return out << f.p << ' ' << f.q;
}

pair<ll, int> tree[4 * maxn];

void upd(int v, int tl, int tr, int p, int val) {
	if (tl == tr) {
		tree[v] = mp(val, 1);
		return;
	}
	int tm = (tl + tr) / 2;
	if (p <= tm) {
		upd(v<<1, tl, tm, p, val);
	} else {
		upd(v<<1|1, tm + 1, tr, p, val);
	}
	tree[v].f = tree[v<<1].f + tree[v<<1|1].f;
	tree[v].s = tree[v<<1].s + tree[v<<1|1].s;
}

pair<ll, int> get(int v, int tl, int tr, ll h) {
	// cout << v << ' ' << tl << ' ' << tr << ' ' << h << endl;
	if (tl == tr) {
		if (h >= tree[v].f) {
			// cout << h << ' ' << tree[v].f << endl;
			return {h - tree[v].f, tree[v].s};
		} else {
			return {h, 0};
		}
	}
	int tm = (tl + tr) / 2;
	if (h >= tree[v<<1].f) {
		auto e = get(v<<1|1, tm + 1, tr, h - tree[v<<1].f);
		return {e.f, e.s + tree[v<<1].s};
	}
	return get(v<<1, tl, tm, h);
}

Frac solve() {
	int n;
	// ifstream cin("input.txt");
	cin >> n;

	for (int i = 0; i < 4 * n; i++) {
		tree[i] = mp(0, 0);
	}

	vector<pii> tmp;

	vector<pair<pii, int>> ra;
	
	for (int i = 0; i < n; i++) {
		int x, y;
		cin >> x >> y;
		tmp.pb(mp(x, i));
		ra.pb(mp(mp(y, x), i));
	}

	sort(all(tmp));

	ll diff = 0;
	sort(all(ra));
	reverse(all(ra));

	vector<ll> fdf(n, 0);


	for (int i = 0; i < n; i++) {
		fdf[i] = diff;
		diff += max(0, ra[i].f.f - ra[i].f.s);
	}

	int max_full = 0;
	Frac max_frac(0, 1);

	for (int i = n - 1; i >= 0; i--) {
		ll hv = fdf[i];
		hv += ra[i].f.f - ra[i].f.s;

		if (hv >= 0) {
			pair<ll, int> cc = get(1, 0, n - 1, hv);
			
			hv = cc.f;	

			Frac cur(min((ll)ra[i].f.f, hv), ra[i].f.f);
			if (cur == Frac(1, 1)) {
				cc.s += 1;
				cur = Frac(0, 1);
			}
			if (cc.s > max_full) {
				max_full = cc.s;
				max_frac = cur;
			} else if (cc.s == max_full) {
				upmax(max_frac, cur);
			}
		}

		auto ps = lower_bound(all(tmp), mp(ra[i].f.s, ra[i].s)) - tmp.begin();
		upd(1, 0, n - 1, ps, ra[i].f.s);
	}

	Frac ans = Frac(max_full, 1) + max_frac;
	ans.norm();
	ans.q *= n;
	ans.norm();
	assert(ans.q > 0);
	return ans;
}

Frac brute() {
	ifstream cin("input.txt");
	int n;
	cin >> n;

	vector<pii> rb;
	for (int i = 0; i < n; i++) {
		int x, y;
		cin >> x >> y;
		rb.pb(mp(x, y));
	}
	sort(all(rb));

	Frac ans(0, 1);
	do {
		ll sa = 0;
		for (int i = 0; i < n; i++) {
			sa += rb[i].f;

			vector<int> bb;
			for (int j = i; j >= 0; j--) {
				bb.pb(rb[j].s);
			}
			int ptr = 0;
			ll tkn = sa;
			while (ptr < bb.size() && tkn >= bb[ptr]) {
				tkn -= bb[ptr];
				ptr++;
			}

			if (ptr < bb.size()) {
				Frac cur((ll)bb.size() - ptr - 1, 1);
				cur += Frac(bb[ptr] - tkn, (ll)bb[ptr]);
				upmax(ans, cur);

				// if (!(cur != Frac(n, 3))) {
				// 	for (int i = 0; i < n; i++) {
				// 		cout << rb[i].f << ' ' << rb[i].s << endl;
				// 	}
				// 	// exit(0);
				// cout << "===="<<endl;
				// }
			}
		}
	} while (next_permutation(all(rb)));
	
	ans.q *= n;
	ans.norm();

	return ans;
}

void gen() {
	ofstream cout("input.txt");
	int n = rand() % 5 + 1;
	cout << n << "\n";
	for (int i = 0; i < n; i++) {
		cout << rand() % 10 + 1 << ' ' << rand() % 10 + 1 << "\n";
	}
}

int main() {
	// files1;
	fast_io;
	// cout << solve() << endl;
	// cout << brute() << endl;
	// return 0;

	cout << solve();
	return 0;

	// for (int iter = 0; iter < 100000; iter++) {
	// 	gen();
	// 	if (iter % 1000 == 0) {
	// 		cout << iter << " ok" << endl;
	// 	}
	// 	if (auto a = solve(), b = brute(); a != b) {
	// 		cout << a << ' ' << b << endl;
	// 		exit(228);
	// 	}
	// }
	return 0;
}

Measured memory_bytes: 9356