#include <bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define mp make_pair
#define gc getchar
#define pb push_back
#define eb emplace_back
#define all(x) (x).begin(),(x).end()
#define x1 uvuvwevwevwe
#define y1 onyetenyevwe
#define x2 ugwemubwem
#define y2 ossas
typedef long long LL;
typedef long double LD;
typedef pair<int,int> pii;
template <typename T> void scan(T &angka){
	angka=0;char input=gc();T kali=1;
	while(!(48<=input&&input<=57)){	if(input=='-')	kali=-1;input=gc();}
	while(48<=input&&input<=57)	angka=(angka<<3)+(angka<<1)+input-48,input=gc();angka*=kali;
}
template <typename T1,typename T2> void scan(T1 &angka1,T2 &angka2){
	scan(angka1);scan(angka2);
}
const int MAXN=100000;
const int MAXNODE=2*MAXN;
const LL mods=1000000007LL;
struct M{
	LL x;
	M(LL angka){
		if(angka>=mods||angka<=-mods)
			angka%=mods;
		if(angka<0)
			angka+=mods;
		x=angka;
	}
	M(){
		x=0;
	}
	M operator + (const M &other) const{
		return M(x+other.x);
	}
	M operator - (const M &other) const{
		return M(x-other.x);
	}
	M operator * (const M &other) const{
		return M(x*other.x);
	}
	M operator - () const{
		return M(-x);
	}
};
int n,rootcc,par[MAXNODE+5],subtree[MAXNODE+5];
M fakt[MAXNODE+5],inv[MAXNODE+5],ans;
vector <int> node[MAXNODE+5],cc,dag[MAXNODE+5];
bool partofcycle[MAXNODE+5],partofcc[MAXNODE+5],rootdag[MAXNODE+5];
void inis(){
	memset(partofcycle,false,sizeof(partofcycle));			//part of cycle gak
	memset(partofcc,false,sizeof(partofcc));				//sudah pernah gak jadi cc
}
void exteuclid(LL a,LL b,M &x,M &y){
	if(b==0)
	{
		x=1;
		y=0;
	}
	else
	{
		exteuclid(b,a%b,x,y);
		M tempx,tempy;
		tempx=x,tempy=y;
		
		x=tempy;
		y=tempx-tempy*(a/b);
	}
}
M invers(LL angka){
	M x,y;
	exteuclid(angka,mods,x,y);
	return x;
}
M invers(M angka){
	return invers(angka.x);
}
void precomp(){
	fakt[0]=1;
	inv[0]=1;
	for(int i=1;i<=MAXNODE;i++)
		fakt[i]=fakt[i-1]*i;
	for(int i=1;i<=MAXNODE;i++)
		inv[i]=invers(fakt[i]);
}
void dfs(int now,int tadi){
	assert(!partofcc[now]);
	partofcc[now]=true;
	cc.pb(now);
	for(auto v:node[now])
	{
		if(v==tadi)
			continue;
		if(partofcc[v])
		{
			if(rootcc==-1)
				rootcc=v;
		}
		else
		{
			dfs(v,now);
		}
	}
}
bool isicc(int s){			//return valid tidak, dan cari root, dan isi cc
	cc.clear();
	rootcc=-1;
	dfs(s,-1);				//isi cc
	assert(rootcc!=-1);
	int total=0;
	for(auto isi:cc)
		total+=(node[isi].size())-1;
	return (total==cc.size());
}
void tentukancycle(int now,int _par){				//sekalian isi parnya, par root itu teman cyclenya
	par[now]=_par;
	for(auto v:node[now])
	{
		if(v==_par)
			continue;
		if(v==rootcc)
		{
			partofcycle[now]=true;
			continue;
		}
		if(now==rootcc&&partofcycle[v])				//berarti sudah visited
		{
			//cout<<"ketemu par"<<endl;
			assert(partofcycle[now]);
			par[now]=v;
			continue;
		}
		tentukancycle(v,now);
		if(partofcycle[v])
		{
			assert(!partofcycle[now]);
			partofcycle[now]=true;
		}
	}
}
void bagidag(int jenis){			//bagi menjadi dag, tergantung loop ke berapa, loop1 based on par, loop2 yang cycle di reverse
	for(auto isi:cc)
	{
		dag[isi].clear();
		int patokan;
		if(jenis==0)
			patokan=par[isi];
		else
		{
			if(partofcycle[isi])
			{
				assert(partofcycle[par[isi]]);
				for(auto v:node[isi])
				{
					if(v==par[isi])
						continue;
					if(partofcycle[v])
					{
						patokan=v;
						break;
					}
				}
			}
			else
				patokan=par[isi];
		}
		
		for(auto v:node[isi])
		{
			if(v<patokan)
				dag[isi].pb(v);
		}
	}
}
void tentukanrootdag(){				//cari root dari dag
	for(auto isi:cc)
		rootdag[isi]=true;
	for(auto isi:cc)
	{
		for(auto v:dag[isi])
			rootdag[v]=false;
	}
}
void hitungsubtree(int now){
	subtree[now]=1;
	for(auto v:dag[now])
	{
		hitungsubtree(v);
		subtree[now]+=subtree[v];
	}
	//cout<<"subtree dari "<<now<<" "<<subtree[now]<<endl;
}
void hitungsubtree(){				//subtree dari dag
	for(auto isi:cc)
	{
		if(rootdag[isi])
			hitungsubtree(isi);
	}
}
M costdag(int now){				//ini sudah jadi dag
	M ret=fakt[subtree[now]-1];
	for(auto v:dag[now])
		ret=ret*costdag(v)*inv[subtree[v]];
	//cout<<"cost dag dari "<<now<<" "<<ret.x<<endl;
	return ret;
}
M costdag(){
	M ret=fakt[cc.size()];
	//cout<<"ret awal "<<ret.x<<endl;
	for(auto isi:cc)
	{
		if(!rootdag[isi])
			continue;
		//cout<<"rootdag "<<subtree[isi]<<" "<<costdag(isi).x<<endl;
		ret=ret*inv[subtree[isi]]*costdag(isi);
	}
	//cout<<"cost akhir "<<ret.x<<endl;
	return ret;
}
void printtree(int now){
	cout<<"anak dari "<<now<<endl;
	for(auto v:dag[now])
		cout<<v<<" ";
	cout<<endl;
	for(auto v:dag[now])
		printtree(v);
}
void solve(){			//bagi jadi beberapa cc
	ans=fakt[2*n];
	for(int i=1;i<=2*n;i++)
	{
		if(partofcc[i])
			continue;
		if(!isicc(i))
		{
			ans=0;
			break;
		}
		//cout<<"rootnya "<<rootcc<<endl;
		tentukancycle(rootcc,-1);
		/*cout<<"ini part of cyclenya "<<endl;
		for(auto isi:cc)
			if(partofcycle[isi])
				cout<<isi<<" ";
		cout<<endl;*/
		M costhere;
		for(int ulang=0;ulang<2;ulang++)
		{
			//cout<<"pengulangan "<<ulang<<endl;
			bagidag(ulang);
			tentukanrootdag();
			/*for(auto isi:cc)
				if(rootdag[isi])
					printtree(isi);*/
			hitungsubtree();
			costhere=costhere+costdag();
		}
		
		//updateans
		ans=ans*costhere*inv[(int) cc.size()];
	}
}
int main()
{
	inis();
	precomp();
	scan(n);
	for(int ulang=0;ulang<2;ulang++)
	{
		for(int i=1;i<=n;i++)
		{
			int x,y;
			scan(x,y);
			node[x].pb(y+n);
			node[y+n].pb(x);
		}
	}
	solve();
	printf("%lld\n",ans.x);
}

Measured memory_bytes: 27508