#include <algorithm>
#include <assert.h>
#include <iostream>
#include <cstring>
#include <vector>
#include <cstdio>
#include <cmath>

using namespace std;

typedef long long ll;

const int MX = 400005;
const ll MOD = 1000000007;

template <typename T> void read(T &x)
{
	x = 0; char c = getchar(); bool f = 0;
	while(!isdigit(c) && c!='-') c = getchar();
	if(c == '-') f = 1, c = getchar();
	while(isdigit(c)) x = x*10+c-'0', c = getchar();
	if(f) x = -x;
}

struct MATH
{
	ll fac[MX], faci[MX], ivx[MX];
	
	void init()
	{
		ivx[0] = ivx[1] = fac[0] = faci[0] = 1;
		for(int i=2; i<MX; i++) ivx[i] = (MOD*MOD - 1ll*(MOD/i)*ivx[MOD%i]) % MOD;
		for(int i=1; i<MX; i++) fac[i] = fac[i-1]*i%MOD, faci[i] = faci[i-1]*ivx[i]%MOD;
	}
	
	ll C(int n, int m)
	{
		if(m>n || m<0 || n<0) return 0;
		else return fac[n] * faci[m] % MOD * faci[n-m] % MOD;
	}
} M;

struct GRAPH
{
	int fst[MX], nxt[MX], v[MX], lnum;
	int ind[MX];
	
	void addeg(int nu, int nv)
	{
		//cout<<nu<<" "<<nv<<endl;
		nxt[++lnum] = fst[nu];
		fst[nu] = lnum;
		v[lnum] = nv;
		ind[nv]++;
	}
} G, H1, H2;

int n;

void input()
{
	int x, y;
	read(n);
	for(int i=1; i<=n*2; i++)
	{
		read(x), read(y);
		G.addeg(x, n+y);
		G.addeg(n+y, x);
	}
}

void check_validity(int x, int *ecnt, int *vcnt, GRAPH &g, int *vis, vector<int> &pot)
{
	pot.push_back(x);
	vis[x] = 1, (*vcnt)++;
	for(int i=g.fst[x]; i; i=g.nxt[i])
	{
		(*ecnt)++;
		if(vis[g.v[i]]) continue;
		else check_validity(g.v[i], ecnt, vcnt, g, vis, pot);
	}
}

int loops, loopt;

void get_loop_tree(int x, int f, GRAPH &g, int *vis)
{
	vis[x] = 1;
	for(int i=g.fst[x]; i; i=g.nxt[i])
	{
		if(vis[g.v[i]] && g.v[i]!=f) loops = x, loopt = g.v[i];
		else if(!vis[g.v[i]]) get_loop_tree(g.v[i], x, g, vis);
	}
}

void cal_subtree(int x, int f, GRAPH &g, GRAPH &h, int *vis)
{
	vis[x] = 1;
	for(int i=g.fst[x]; i; i=g.nxt[i])
	{
		if(!vis[g.v[i]] && g.v[i] != f)
		{
			cal_subtree(g.v[i], x, g, h, vis);
			if(g.v[i] < f) h.addeg(x, g.v[i]);
		}
		else if(x==loops && g.v[i]==loopt && g.v[i]<f) h.addeg(x, g.v[i]);
		else if(x==loopt && g.v[i]==loops && g.v[i]<f) h.addeg(x, g.v[i]);
	}
}

int siz[MX];

ll cal_topo(int x, int f, const GRAPH &g)
{
	ll ret = 1;
	siz[x] = 1;
	for(int i=g.fst[x]; i; i=g.nxt[i])
		if(!siz[g.v[i]])
		{
			ret = ret * cal_topo(g.v[i], x, g) % MOD;
			siz[x] += siz[g.v[i]];
		}
	return ret * M.ivx[siz[x]] % MOD;
}

ll get_way(const GRAPH &h, const vector<int> &pot)
{
	ll tmp = 1;
	for(auto p : pot) siz[p] = 0;
	for(auto p : pot)
		if(!siz[p] && !h.ind[p])
			tmp = tmp * cal_topo(p, 0, h) % MOD;
	return tmp % MOD;
}

int vis1[MX], vis2[MX], vis3[MX], vis4[MX];

void work()
{
	ll ans = 1;
	int tot = 0;
	for(int i=1; i<=n*2; i++)
	{
		vector<int> pot;
		ll tmp = 0;
		int ecnt = 0, vcnt = 0;
		if(!vis1[i])
		{
			check_validity(i, &ecnt, &vcnt, G, vis1, pot);
			if(ecnt != vcnt*2) ans = 0;
			get_loop_tree(i, 0, G, vis2);
			//cout<<"constructing T1:"<<endl;
			cal_subtree(loops, loopt, G, H1, vis3);
			//cout<<"constructing T2:"<<endl;
			cal_subtree(loopt, loops, G, H2, vis4);
			tmp = get_way(H1, pot) + get_way(H2, pot);
			ans = ans * M.fac[vcnt] % MOD * tmp % MOD * M.C(tot+vcnt, tot) % MOD;
			tot += vcnt;
		}
	}
	printf("%lld\n", ans);
}

int main()
{
	M.init();
	input();
	work();
	return 0;
}

Measured memory_bytes: 41204