#include<iostream>
#include<string>
#include<cstdio>
#include<vector>
#include<cmath>
#include<algorithm>
#include<functional>
#include<iomanip>
#include<queue>
#include<ciso646>
#include<random>
#include<map>
#include<set>
#include<bitset>
#include<stack>
#include<unordered_map>
#include<utility>
#include<cassert>
#include<complex>
using namespace std;

//#define int long long
typedef long long ll;

typedef unsigned long long ul;
typedef unsigned int ui;
const ll mod = 1000000007;
const ll INF = mod * mod;
typedef pair<int, int>P;
#define stop char nyaa;cin>>nyaa;
#define rep(i,n) for(int i=0;i<n;i++)
#define per(i,n) for(int i=n-1;i>=0;i--)
#define Rep(i,sta,n) for(int i=sta;i<n;i++)
#define rep1(i,n) for(int i=1;i<=n;i++)
#define per1(i,n) for(int i=n;i>=1;i--)
#define Rep1(i,sta,n) for(int i=sta;i<=n;i++)
typedef pair<ll, ll> LP;
typedef long double ld;
typedef pair<ld, ld> LDP;
const ld eps = 1e-6;
const ld pi = acos(-1.0);
typedef vector<vector<ll>> mat;
typedef vector<ll> vec;

void debug(vec &v) {
	int n = v.size();
	rep(i, n) {
		if (i > 0)cout << " ";
		cout << v[i];
	}
	cout << "\n";
}

ll mod_pow(ll x, ll n) {
	ll ret = 1;
	while (n > 0) {
		if (n % 2)ret = ret * x%mod;
		x = x * x%mod; n >>= 1;
	}
	return ret;
}

struct uf {
private:
	vector<int> par, ran;
public:
	uf(int n) {
		par.resize(n, 0);
		ran.resize(n, 0);
		rep(i, n) {
			par[i] = i;
		}
	}
	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 (ran[x] < ran[y]) {
			par[x] = y;
		}
		else {
			par[y] = x;
			if (ran[x] == ran[y])ran[x]++;
		}
	}
	bool same(int x, int y) {
		return find(x) == find(y);
	}
};

vector<P> vx[1 << 17], vy[1 << 17];
int memo[1 << 18];
int chk[1 << 18];
int cop[1 << 18];

bool used[1 << 18];

struct edge {
	int to, id;
};
vector<edge> G[1 << 18];

vector<int> fG[1 << 18];
int n;
bool visited[1 << 18];
ll calc(vector<int> &v) {
	ll ret = 1;
	vector<pair<P, int>> ids;
	rep(i, v.size()) {
		int val = v[i];
		if (val < n) {
			int x = val;
			rep(j, vx[x].size()) {
				P z = vx[x][j];
				if (chk[z.second] == 0) {
					fG[z.second].clear();
					for (int k = j - 1; k >= 0; k--) {
						P zz = vx[x][k];
						fG[z.second].push_back(zz.second);
					}
					ids.push_back({ {x,z.first},z.second });
					visited[z.second] = false;
					break;
				}
			}
		}
		else {
			int y = val - n;
			rep(j, vy[y].size()) {
				P z = vy[y][j];
				if (chk[z.second] == 1) {
					fG[z.second].clear();
					for (int k = j - 1; k >= 0; k--) {
						P zz = vy[y][k];
						fG[z.second].push_back(zz.second);
					}
					ids.push_back({ {z.first,y }, z.second });
					visited[z.second] = false;
					break;
				}
			}
		}
	}
	sort(ids.begin(), ids.end(), greater<pair<P, int>>());
	function<int(int, int)> dfs = [&](int id, int fr)->int {
		int res = 1;
		visited[id] = true;
		rep(j, fG[id].size()) {
			int to = fG[id][j];
			if (to == fr)continue;
			res += dfs(to, id);
		}
	    ret = ret * res%mod;
		return res;
	};
	rep(i, ids.size()) {
		int id = ids[i].second;
		if (!visited[id])dfs(id, -1);
	}
	return mod_pow(ret,mod-2);
}
void solve() {
	cin >> n;
	uf u(2 * n);
	rep(i, 2 * n) {
		int x, y; cin >> x >> y; x--; y--;
		vx[x].push_back({ y,i });
		vy[y].push_back({ x,i });
		chk[i] = -1;
		u.unite(x, y + n);
	}
	vector<vector<int>> vs;
	{
		vector<vector<int>> loc(2 * n);
		rep(i, 2 * n) {
			loc[u.find(i)].push_back(i);
		}
		rep(i, 2 * n)if (loc[i].size() > 0)vs.push_back(loc[i]);
	}
	
	priority_queue<P, vector<P>, greater<P>> q;
	rep(i, n) {
		sort(vx[i].begin(), vx[i].end());
		sort(vy[i].begin(), vy[i].end());
		q.push({ vx[i].size(),i });
		q.push({ vy[i].size(), i + n });
		used[i] = used[i + n] = false;
		memo[i] = vx[i].size();
		memo[i + n] = vy[i].size();
	}

	while (!q.empty()) {
		P p = q.top(); q.pop();
		//cout << p.first << endl;
		if (p.first >= 2)break;
		int id = p.second;
		if (used[id])continue;
		used[id] = true;
		if (p.first == 0) {
			cout << 0 << endl; return;
		}
		if (id < n) {
			rep(j, vx[id].size()) {
				P z = vx[id][j];
				if (chk[z.second] >= 0)continue;
				chk[z.second] = 0;
				int y = z.first; y += n;
				memo[y]--;
				q.push({ memo[y],y });
			}
		}
		else {
			rep(j, vy[id-n].size()) {
				P z = vy[id - n][j];
				if (chk[z.second] >= 0)continue;
				chk[z.second] = 1;
				int x = z.first;
				memo[x]--;
				q.push({ memo[x],x });
			}
		}
	}
	ll pro = 1;
	rep(i, 2 * n)cop[i] = chk[i];
	rep(aa, vs.size()) {
		//cout << vs[aa].size() << endl;
		vector<int> xs, ys;
		rep(j, vs[aa].size()) {
			int val = vs[aa][j];
			if (val < n && !used[val])xs.push_back(val);
			if (val >= n && !used[val])ys.push_back(val - n);
		}
		assert(xs.size() == ys.size());
		//cout << "hello" << endl;
		//rep(i, xs.size())cout << xs[i] << endl;
		//rep(i, ys.size())cout << ys[i] << endl;
		if (xs.size() == 0) {
			pro = pro * calc(vs[aa]) % mod;
			continue;
		}
		rep(i, xs.size()) {
			int x = xs[i];
			rep(j, vx[x].size()) {
				P z = vx[x][j];
				if (cop[z.second] < 0) {
					G[z.first + n].push_back({ x,z.second });
					G[x].push_back({ z.first + n,z.second });
				}
			}
		}
		vector<int> roopid;
		int cur = xs[0];
		int to = G[cur][0].to;
		roopid.push_back(G[cur][0].id);
		rep(i, 2 * xs.size() - 1) {
			if (G[to][0].to == cur) {
				roopid.push_back(G[to][1].id);
				cur = to; to = G[to][1].to;
			}
			else {
				roopid.push_back(G[to][0].id);
				cur = to; to = G[to][0].to;
			}
		}
		ll sum = 0;
		rep(j, 2) {
			rep(i, roopid.size()) {
				int id = roopid[i];
				if ((i + j) % 2)chk[id] = 0;
				else chk[id] = 1;
			}
			sum += calc(vs[aa]);
		}
		sum %= mod;
		pro = pro * sum%mod;
	}
	for (int i = 1; i <= 2 * n;i++)pro = pro * i%mod;
	cout << pro << endl;
}


signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	//cout << fixed << setprecision(17);
	//init();
	//int t; cin >> t; rep(i, t)solve();
	solve();
	stop
		return 0;
}

Measured memory_bytes: 42008