#include <bits/stdc++.h>
// iostream is too mainstream
#include <cstdio>
// bitch please
#include <iostream>
#include <algorithm>
#include <cstdlib>
#include <vector>
#include <set>
#include <map>
#include <queue>
#include <stack>
#include <list>
#include <cmath>
#include <iomanip>
#include <time.h>
#define dibs reserve
#define OVER9000 1234567890123456789LL
#define ALL_THE(CAKE,LIE) for(auto LIE =CAKE.begin(); LIE != CAKE.end(); LIE++)
#define tisic 47
#define soclose 1e-8
#define chocolate win
// so much chocolate
#define patkan 9
#define ff first
#define ss second
#define abs(x) ((x < 0)?-(x):x)
#define uint unsigned int
#define dbl long double
#define pi 3.14159265358979323846
using namespace std;
// mylittledoge

typedef long long cat;

#ifdef DONLINE_JUDGE
	// palindromic tree is better than splay tree!
	#define lld I64d
#endif

cat pw(cat a, cat e, cat mod) {
	if(e <= 0) return 1;
	cat x =pw(a,e/2,mod);
	x =(x*x)%mod;
	if(e&1) x =(x*a)%mod;
	return x;
}

cat mod =1000000007;
vector<cat> fac;
vector< vector<int> > GT;
vector<int> DT;

cat DFST(int R, cat &ret) {
	cat S =0;
	ALL_THE(GT[R],it) {
		cat Ss =DFST(*it,ret);
		S +=Ss;
		ret =(ret*pw(fac[Ss],mod-2,mod))%mod;
	}
	ret =(ret*fac[S])%mod;
	S++;
	return S;
}

cat count(vector<int> &comp, vector<int> &par, vector< vector<int> > &G) {
	ALL_THE(comp,it) {
		GT[*it].clear();
		DT[*it] =0;
	}
	ALL_THE(comp,it) ALL_THE(G[*it],jt) if(*jt < par[*it]) {
		GT[*it].push_back(*jt);
		DT[*jt]++;
	}
	cat ret =1;
	int S =0;
	ALL_THE(comp,it) if(DT[*it] == 0) {
		int Ss =DFST(*it,ret);
		ret =(ret*pw(fac[Ss],mod-2,mod))%mod;
		S +=Ss;
	}
	ret =(ret*fac[S])%mod;
	return ret;
}

void DFS(int R, vector< vector<int> > &G, vector<int> &par, vector<int> &vis, vector<int> &comp, int &on_cyc) {
	comp.push_back(R);
	ALL_THE(G[R],it) {
		if(par[R] != *it) {
			if(par[*it] == -1) par[*it] =R;
			if(vis[*it]) on_cyc =*it;
		}
		if(vis[*it] == 0) {
			vis[*it] =1;
			DFS(*it,G,par,vis,comp,on_cyc);
		}
	}
}

int main() {
	cin.sync_with_stdio(0);
	cin.tie(0);
	cout << fixed << setprecision(10);
	int N;
	cin >> N;
	GT.resize(2*N);
	DT.resize(2*N);
	vector< vector<int> > G(2*N);
	for(int i =0; i < 2*N; i++) {
		int x,y;
		cin >> x >> y;
		G[--x].push_back(--y+N);
		G[y+N].push_back(x);
	}
	fac.resize(2*N+1,1);
	for(int i =1; i <= 2*N; i++) fac[i] =(i*fac[i-1])%mod;
	cat ans =fac[2*N];

	vector<int> vis(2*N,0), par(2*N,-1);
	for(int i =0; i < 2*N; i++) if(vis[i] == 0) {
		vector<int> comp;
		vis[i] =1;
		int on_cyc =-1;
		DFS(i,G,par,vis,comp,on_cyc);
		if(on_cyc == -1) {
			cout << "0\n";
			return 0;
		}
		int e =0;
		ALL_THE(comp,it) {
			e +=G[*it].size();
			par[*it] =-1;
			vis[*it] =0;
		}
		if(e/2 != (int)comp.size()) {
			cout << "0\n";
			return 0;			
		}
		comp.clear();
		int v =on_cyc;
		vis[on_cyc] =1;
		DFS(v,G,par,vis,comp,on_cyc);
		vector<int> cyc;
		int a =par[v];
		while(true) {
			cyc.push_back(a);
			if(a == v) break;
			a =par[a];
		}

		cat akt =count(comp,par,G);
		for(int j =0; j < (int)cyc.size(); j++) par[cyc[(j+1)%cyc.size()]] =cyc[j];
		akt +=count(comp,par,G);

		ans =(ans*akt)%mod;
		ans =(ans*pw(fac[comp.size()],mod-2,mod))%mod;
	}

	if(ans < 0) ans +=mod;
	cout << ans << "\n";
	return 0;}

// look at my code
// my code is amazing


Measured memory_bytes: 28120