#include <vector> 
#include <list> 
#include <map>
#include <set>
#include <deque>
#include <stack>
#include <bitset>
#include <algorithm>
#include <functional>
#include <numeric>
#include <utility>
#include <sstream>
#include <iostream>
#include <iomanip>
#include <cstdio>
#include <cmath>
#include <cstdlib>
#include <cctype>
#include <string>
#include <cstring>
#include <ctime>
#include <queue>
using namespace std;

//conversion
//------------------------------------------
inline int toInt(string s) { int v; istringstream sin(s); sin >> v; return v; }
template<class T> inline string toString(T x) { ostringstream sout; sout << x; return sout.str(); }

//math
//-------------------------------------------
template<class T> inline T sqr(T x) { return x * x; }

//typedef
//------------------------------------------
typedef vector<int> VI;
typedef vector<VI> VVI;
typedef vector<string> VS;
typedef pair<int, int> PII;
typedef long long LL;

//container util
//------------------------------------------
#define ALL(a)  (a).begin(),(a).end()
#define RALL(a) (a).rbegin(), (a).rend()
#define PB push_back
#define MP make_pair
#define SZ(a) int((a).size())
#define EACH(i,c) for(typeof((c).begin()) i=(c).begin(); i!=(c).end(); ++i)
#define EXIST(s,e) ((s).find(e)!=(s).end())
#define SORT(c) sort((c).begin(),(c).end())

//repetition
//------------------------------------------
#define FOR(i,a,b) for(int i=(a);i<(b);++i)
#define REP(i,n)  FOR(i,0,n)

//constant
//--------------------------------------------
const double EPS = 1e-10;
const double PI = acos(-1.0);

//clear memory
#define CLR(a) memset((a), 0 ,sizeof(a))

//debug
#define dump(x)  cerr << #x << '=' << (x) << endl;
#define debug(x) cerr << #x << '=' << (x) << '('<<'L' << __LINE__ << ')' << ' ' << __FILE__ << endl;
/*
# 最大流問題

** s(スタート)から出てt(終点)へ到達するデータ量を最大化しろという問題 **

## 問題設定
・各辺に対してデータを通信する量をf(e)とする。
・通信量は以下の制約を満たす必要がある
0<= f(e)<=c(e)
・データは途中で増えたり減ったりしないので、入ってくるデータ量と出ていくデータ量は等しくなる



## まずは貪欲に考えてみる（良くない解法）


1   f(e)<c(e)であるeのみを用いたsからtへのパスを見つける
2　そのようなパスが存在しなければ終了。　パスが存在すればそのパスに沿って目一杯に流して１へ戻る

**これではうまくいかない**
パスを見つける順番によって最善にならなかったりする

## Ford-Fulkerson法

先ほどの貪欲法を以下のように改善する


1   f(e)<c(e)であるeと f(e)>0であるeの逆辺(1→2に対する2→1のこと)　を用いたsからtへのパスを見つける
2　そのようなパスが存在しなければ終了。　パスが存在すればそのパスに沿って目一杯に流して１へ戻る

逆辺を使ったパスが見つかればそれを使ってまだsからtへ流す事が出来る。

f(e)<c(e)であるeと f(e)>0であるeの逆辺からなるグラフを**残余グラフ**という

**これでパスを見つける順番によらずに最善を見つける事が出来る**

## カットについて

グラフのカットとはある頂点集合Sから出ていく辺の集合**（入ってくるものは含まない）**のことで（S,V/S）と表現される(Vは頂点全体の集合)
またカットの容量の和をカットの容量という
特にs∈S, t∈V/Sのときこのカットをs-tカットと呼ぶ

## 最小カット

最小カット問題とは
s-tカットの容量の最小値はどれだけかという問題。

s-tに流せる流量というのは最大でもs-tカットの容量分しか流せないわけなのでつまりこの最小カットのサイズが最大流量ということになる。

ここでFord-Fulkerson法で求めたフローf'について考える。
もしこれが最善になっていればフローf'で流すことのできる流量は最小カットのサイズと一致するはずである。

Ford-Fulkerson法は残余グラフを消すように動くので
f'の残余グラフはs-tがつながっていない。つまりs-tカットになっている。
ここでS→V/Sの辺はf(e)=c(e)になっており,V/S→Sの辺はf(e)==0になるようにFord-Fulkerson法は動くのでこの流量は最小カットのサイズと一致する。
つまりFord-Fulkerson法はちゃんと最善を求める事が出来ている。

*/
struct FLOW {

	//行先　容量　逆辺
	struct edge { int to, cap, rev; };

	int MAX_V;
	vector< vector<edge> > G;//グラフの隣接リスト表現
	vector<bool> used;//DFSですでに調べたかのフラグ
	int INF = INT32_MAX - 1;
	FLOW(int V) {
		MAX_V = V;
		G.resize(V);
		used.resize(V);
	}

	void add_edge(int from, int to, int cap) {
		G[from].push_back(edge{ to,cap,(int)G[to].size() });//to 容量 逆辺のindex
		G[to].push_back(edge{ from,0,(int)G[from].size() - 1 });//to 容量 辺のindex
	}

	//ｓからｔへの最大流を求める
	int max_flow(int s, int t) {
		int flow = 0;
		while (true) {
			REP(i, SZ(used)) { used[i] = false; }
			int f = dfs(s, t, INF);//最初はINF流そうと試みる
			if (f == 0) { return flow; }//流量が変化しなかった
			flow += f;
		}
	}

	//深さ優先探索で増加パスを探す
	//ここではf(e)を計算する代わりにキャパシティーを変化させている。
	int dfs(int v, int t, int f) {

		if (v == t) { return f; }//終点に到着した

		used[v] = true;

		vector<edge>& G_ = G[v];//頂点vから到達できる頂点の集合
		REP(i, SZ(G_)) {
			edge& e = G_[i];
			//まだ訪問されておらず、キャパシティーも残っている
			if (!used[e.to] && e.cap>0) {
				//dは実際に最後まで通り抜けることができた流量           min(f,e.cap)ここを通り抜けることができるのは最大でもこの辺のキャパシティー分だけ
				int d = dfs(e.to, t, min(f, e.cap));
				if (d>0) {
					//tまでちゃんと到達することが場合
					//このedgeが逆辺だった場合と普通の辺だった場合があるのでちょっとわけて考えないといけないな...
					/*
					普通の辺だった場合
					f(e)+=dなのでc(e)-=d 逆辺はf(e)のことなのでrev(e)+=d
					逆辺だった場合
					これまでのフローを押し戻して別のpathを通してその分を流すことになる
					*/
					e.cap -= d;
					G[e.to][e.rev].cap += d;
					return d;
				}
			}
		}
		return 0;
	}
};

/*
2部マッチング

合コンの話を考える
女の子の集団をUとし男の子の集団をVとする
女の子にはこの人とならペアを組んでもいいというひとが数人いる
最大で何ペアを作れるか

グラフの辺をUからVの方向に向け　容量を1とする
この人とならペアを組んでもいいという人に対して辺を張る

始点sと終点tをグラフに加えて
sからすべてのUの頂点へ容量１の辺を張り、
tへすべてのVの頂点から容量１の辺を張る。

このグラフにおける最大フローが
もとのマッチングの最大ペア数となる。

*/
int N, K;
bool can[101][101];

void solve() {

	FLOW flow(300);

	//0~N-1 ：　女の子に対応する頂点
	//N~N+K-1 : 男の子に対応する頂点
	int s = N + K, t = s + 1;//s:始点　ｔ　終点


							 //始点と女の子をつなげる
	REP(i, N) {
		//from to cap
		flow.add_edge(s, i, 1);
	}

	//終点と男の子をつなげる
	REP(i, K) {
		flow.add_edge(N + i, t, 1);
	}

	REP(i, N)REP(j, K) {

		if (can[i][j]) {
			flow.add_edge(i, N + j, 1);
		}
	}
	cout << flow.max_flow(s, t);
}



int main() {
	CLR(can);
	cin >> N;
	K = N;
	vector< PII > a(N), b(K);
	REP(i, N) {
		cin >> a[i].first >> a[i].second;
	}
	REP(i, K) {
		cin >> b[i].first >> b[i].second;
	}
	REP(i, N)REP(j, K) {
		if (a[i].first < b[j].first&&a[i].second < b[j].second) {
			can[i][j] = true;
		}
	}

	solve();


}


Measured memory_bytes: 384