#include <iostream>
#include <vector>
#include <algorithm>
#include <queue>
using namespace std;
#pragma warning (disable: 4996)

long long modpow(long long a, long long b, long long m) {
	long long p = 1, q = a;
	for (int i = 0; i < 33; i++) {
		if ((b & (1LL << i)) != 0) { p *= q; p %= m; }
		q *= q; q %= m;
	}
	return p;
}

long long Div(long long a, long long b, long long m) {
	return (a * modpow(b, m - 2, m)) % m;
}

long long N, X[1 << 18], Y[1 << 18], dir[1 << 18];
long long cntX[1 << 18], cntY[1 << 18];
vector<pair<long long, long long> > GX[1 << 17], GY[1 << 17];
vector<int> G[1 << 18], G2[1 << 18];
int pre_G[1 << 18], pre_G2[1 << 18];

const long long mod = 1000000007;
long long cur[1 << 18];
long long ret = 0;
long long ret2 = 0;
vector<int> E, E2;

int dfs(int pos) {
	int mi = 1;
	for (int i : G[pos]) mi += dfs(i);
	ret *= mi; ret %= mod;
	return mi;
}

int dfs2(int pos) {
	int mi = 1;
	for (int i : G[pos]) mi += dfs2(i);
	ret *= mi; ret %= mod;
	ret2 *= cur[pos]; ret2 %= mod;
	return mi;
}

void add_edge(int pos) {
	if (dir[pos] == 1) {
		int pos1 = lower_bound(GX[X[pos]].begin(), GX[X[pos]].end(), make_pair(Y[pos], 1LL * pos)) - GX[X[pos]].begin();
		for (int j = 0; j < pos1; j++) { G[pos].push_back(GX[X[pos]][j].second); G2[GX[X[pos]][j].second].push_back(pos); E2.push_back(GX[X[pos]][j].second); }
	}
	if (dir[pos] == 2) {
		int pos1 = lower_bound(GY[Y[pos]].begin(), GY[Y[pos]].end(), make_pair(X[pos], 1LL * pos)) - GY[Y[pos]].begin();
		for (int j = 0; j < pos1; j++) { G[pos].push_back(GY[Y[pos]][j].second); G2[GY[Y[pos]][j].second].push_back(pos); E2.push_back(GY[Y[pos]][j].second); }
	}
}

int main() {
	// ステップ 1: 入力・前処理
	scanf("%lld", &N);
	for (int i = 1; i <= 2 * N; i++) {
		scanf("%lld%lld", &X[i], &Y[i]);
		cntX[X[i]]++; GX[X[i]].push_back(make_pair(Y[i], i));
		cntY[Y[i]]++; GY[Y[i]].push_back(make_pair(X[i], i));
	}
	for (int i = 1; i <= N; i++) sort(GX[i].begin(), GX[i].end());
	for (int i = 1; i <= N; i++) sort(GY[i].begin(), GY[i].end());
	for (int i = 1; i <= N; i++) { if (cntX[i] == 0) { cout << "0" << endl; return 0; } }
	for (int i = 1; i <= N; i++) { if (cntY[i] == 0) { cout << "0" << endl; return 0; } }

	// ステップ 2: ある程度決める
	queue<pair<int, int>> Q;
	for (int i = 1; i <= N; i++) { if (cntX[i] == 1) Q.push(make_pair(1, i)); }
	for (int i = 1; i <= N; i++) { if (cntY[i] == 1) Q.push(make_pair(2, i)); }
	while (!Q.empty()) {
		int pos1 = Q.front().first, pos2 = Q.front().second; Q.pop();
		int num = -1;
		if (pos1 == 1) { for (pair<long long, long long> i : GX[pos2]) { if (dir[i.second] == 0) num = i.second; } }
		if (pos1 == 2) { for (pair<long long, long long> i : GY[pos2]) { if (dir[i.second] == 0) num = i.second; } }
		dir[num] = pos1; cntX[X[num]]--; cntY[Y[num]]--;
		if (pos1 == 1) { if (cntY[Y[num]] == 1) Q.push(make_pair(2, Y[num])); }
		if (pos1 == 2) { if (cntX[X[num]] == 1) Q.push(make_pair(1, X[num])); }
	}

	// ステップ 3: 既に決まったところに辺を貼る
	for (int i = 1; i <= 2 * N; i++) {
		if (dir[i] == 0) continue;
		add_edge(i);
	}
	for (int i = 1; i <= 2 * N; i++) pre_G[i] = G[i].size();
	for (int i = 1; i <= 2 * N; i++) pre_G2[i] = G2[i].size();

	// ステップ 4: 幅優先探索で確率を求める
	long long val = 1;
	for (int i = 1; i <= 2 * N; i++) {
		if (G2[i].size() >= 1) { cur[i] = 1; continue; }
		ret = 1; dfs(i);
		cur[i] = ret; val *= ret; val %= mod;
	}
	long long Answer = Div(1, val, mod);

	// ステップ 5: それぞれの場合に計算する
	for (int i = 1; i <= N; i++) {
		if (cntX[i] == 0) continue;

		// 最終的に記録しておく割合
		long long wari1 = 0;
		long long wari2 = 0;

		// 方向が 1 の場合
		E.clear(); E2.clear(); Q.push(make_pair(1, i));
		while (!Q.empty()) {
			int pos1 = Q.front().first, pos2 = Q.front().second; Q.pop();
			int num = -1;
			if (pos1 == 1) { for (pair<long long, long long> i : GX[pos2]) { if (dir[i.second] == 0) num = i.second; } }
			if (pos1 == 2) { for (pair<long long, long long> i : GY[pos2]) { if (dir[i.second] == 0) num = i.second; } }
			dir[num] = pos1; cntX[X[num]]--; cntY[Y[num]]--; E.push_back(num);
			if (pos1 == 1) { if (cntY[Y[num]] == 1) Q.push(make_pair(2, Y[num])); }
			if (pos1 == 2) { if (cntX[X[num]] == 1) Q.push(make_pair(1, X[num])); }
		}
		for (int j : E) add_edge(j);
		ret = 1; ret2 = 1;
		for (int j : E) {
			if (G2[j].size() >= 1) continue;
			dfs2(j);
		}
		wari1 = Div(ret, ret2, mod);
		for (int j : E) { while (G[j].size() > pre_G[j]) G[j].pop_back(); }
		for (int j : E2) { while (G2[j].size() > pre_G2[j]) G2[j].pop_back(); }
		E2.clear();

		// 方向が 2 の場合
		for (int j : E) dir[j] = 3 - dir[j];
		for (int j : E) add_edge(j);
		ret = 1; ret2 = 1;
		for (int j : E) {
			if (G2[j].size() >= 1) continue;
			dfs2(j);
		}
		wari2 = Div(ret, ret2, mod);
		for (int j : E) { while (G[j].size() > pre_G[j]) G[j].pop_back(); }
		for (int j : E2) { while (G2[j].size() > pre_G2[j]) G2[j].pop_back(); }
		E2.clear();

		// 最終的な計算
		Answer *= (Div(1, wari1, mod) + Div(1, wari2, mod));
		Answer %= mod;
	}

	for (int i = 1; i <= 2 * N; i++) {
		Answer *= (1LL * i); Answer %= mod;
	}

	cout << Answer << endl;
	return 0;
}

Measured memory_bytes: 49272