#include <vector>
#include <cstdio>
#include <algorithm>

using namespace std;
const int MOD(1000000000 + 7);
typedef long long int LL;

void Get_Val(int &Ret)
{
	Ret = 0;
	char ch;
	while (ch = getchar(), ch > '9' || ch < '0')
		;
	do
	{
		(Ret *= 10) += ch - '0';
	}
	while (ch = getchar(), ch >= '0' && ch <= '9');
}

inline int Mult(const int &a, const int &b)
{
	return (a * 1LL) * b % MOD;
}

inline void update(int &a, const int &b)
{
	((a += b) >= MOD) ? (a -= MOD) : 0;
}

void exgcd(const int &a, const int &b, int &x, int &y)
{
	if (b == 0)
		x = 1, y = 0;
	else
		exgcd(b, a % b, y, x), y -= x * (a / b);
}

inline int inverse(const int &a)
{
	int invx, invy;
	exgcd(a, MOD, invx, invy);
	return (invx % MOD + MOD) % MOD;
}

//把x轴上的机器人当成X集合的点，把y轴上的机器人当成Y集合的点，一个球(xi, yi)当成一条连接X[xi]和Y[yi]的一条边，边权为xi + yi
//这样就构成了一个点数为2N边数为2N的图，每次激活一个机器人等价于删去对应的点和与该点相连的未被删除的边权最小的边（唯一）
//考虑每个联通块，必有边数 <= 点数，由于整个图边数 = 点数，所以每个联通块均有边数 = 点数，即是一个基环树
//我们可以得出结论：每个点都与一条边一一对应
//树上的点显然只有唯一的对应方式，即每个点对应自己的父亲边。而环上也只有两种对应方式，可以枚举是哪一种 
//考虑x期望对应的边e，那么删去x时不能存在边权比e小的边，即所有与x相连边权比e小的边的连接的点y都要先于x删除 
//对于这样的(x, y)，我们从x向y连边，这样一定得到了一个森林 
//问题转化成给树上每个点标上连续且互不相等的标号使得父亲的标号小于孩子的标号，直接树形dp计数即可 

const int Max_N(100050);

int N, Fac[Max_N << 1], Inv[Max_N << 1];

inline int C(const int &n, const int &m)
{
	return (n < m) ? 0 : Mult(Fac[n], Mult(Inv[m], Inv[n - m]));
}

namespace T
{
	int Head[Max_N << 1], Total, From[Max_N << 1], To[Max_N << 1], Next[Max_N << 1];
	int F[Max_N << 1], Size[Max_N << 1];
	bool In[Max_N << 1];
	void clear()
	{
		for (int i = 1;i <= Total;++i)
			Head[From[i]] = In[To[i]] = false, From[i] = To[i] = Next[i] = 0;
		Total = 0;
	}
	inline void Add_Edge(const int &s, const int &t)
	{
		++Total, From[Total] = s, To[Total] = t, Next[Total] = Head[s], Head[s] = Total, In[t] = true;
	}
	void dp(const int &u)
	{
		F[u] = 1, Size[u] = 0;
		for (int i = Head[u], v;i;i = Next[i])
			dp(v = To[i]), F[u] = Mult(F[u], Mult(F[v], C(Size[u] += Size[v], Size[v])));
		++Size[u];
	}
}

namespace G
{
	struct edge
	{
		edge(const int &_t = 0, const int &_w = 0) : t(_t), w(_w) {}
		int t, w;
	};
	inline bool comp(const edge &a, const edge &b)
	{
		return a.w < b.w;
	}
	int Head[Max_N << 1], Total, To[Max_N << 2], Next[Max_N << 2], Weight[Max_N << 2], A, B;
	int dfsclock, Pre[Max_N << 1];
	bool Cut[Max_N << 2], done[Max_N << 1];
	vector<edge> E[Max_N << 1];
	inline void Add_Edge(const int &tot, const int &s, const int &t, const int &w)
	{
		To[tot] = t, Next[tot] = Head[s], Head[s] = tot, Weight[tot] = w, E[s].push_back(edge(t, w));
	}
	inline void Add_Link(const int &u, const int &v, const int &w)
	{
		Total += 2, Add_Edge(Total, u, v, w), Add_Edge(Total ^ 1, v, u, w);
	}
	int Tarjan(const int &u, const int &fa)
	{
		int lowu(Pre[u] = ++dfsclock), lowv;
		++A;
		for (int i = Head[u], v;i;i = Next[i])
		{
			++B;
			if (!Pre[v = To[i]])
			{
				lowu = min(lowu, lowv = Tarjan(v, u));
				if (lowv > Pre[u])
					Cut[i] = Cut[i ^ 1] = true;
			}
			else
				if (v != fa && Pre[v] < Pre[u])
					lowu = min(lowu, Pre[v]);
		}
		return lowu;
	}
	int Tot, V[Max_N << 1], Sum, NowSum, dpAns, go[Max_N << 1];
	void dfscircle(const int &u)
	{
		done[V[++Tot] = u] = true;
		for (int i = Head[u], v;i;i = Next[i])
			if (!done[v = To[i]] && !Cut[i])
				dfscircle(v);
	}
	void insert(const int &x, const int &eto)
	{
		for (int i = 0;i < E[x].size();++i)
			if (E[x][i].t == eto)
				break;
			else
				T::Add_Edge(x, E[x][i].t);
	}
	void maketree(const int &u, const int &fa)
	{
		for (int i = Head[u], v;i;i = Next[i])
			if (Cut[i] && (v = To[i]) != fa)
				maketree(v, u), insert(v, u), ++NowSum;
	}
	void dp(const int &u, const int &gg)
	{
		if (go[u] == gg)
			return;
		++go[u];
		if (!T::In[u])
			T::dp(u), dpAns = Mult(dpAns, Mult(T::F[u], C(NowSum += T::Size[u], T::Size[u])));
		for (int i = Head[u];i;i = Next[i])
			dp(To[i], gg);
	}
	void work()
	{
		int Ans(1);
		for (int i = 1;i <= (N << 1);++i)
		{
			sort(E[i].begin(), E[i].end(), comp);
			if (!Pre[i])
				A = B = 0, Tarjan(i, -1), B /= 2;
			if (A != B)
			{
				printf("0");
				return;
			}
		}
		for (int u = 1, val;u <= (N << 1);++u)
			if (!done[u])
			{
				Tot = val = 0, dfscircle(u);
				if (Tot >= 2)
				{
					T::clear();
					for (int i = 1;i <= Tot;++i)
					{
						maketree(V[i], -1);
						if (i - 1 >= 1)
							insert(V[i], V[i - 1]);
						else
							insert(V[i], V[Tot]);
					}
					NowSum = 0, dpAns = 1, dp(V[1], 1), update(val, dpAns);
					T::clear();
					for (int i = 1;i <= Tot;++i)
					{
						maketree(V[i], -1);
						if (i + 1 <= Tot)
							insert(V[i], V[i + 1]);
						else
							insert(V[i], V[1]);
					}
					NowSum = 0, dpAns = 1, dp(V[1], 2), update(val, dpAns);
					Ans = Mult(Ans, Mult(val, C(Sum += NowSum, NowSum)));
				}
			}
		printf("%d", Ans);
	}
}

int main()
{
	Get_Val(N);
	for (int i = 1, x, y;i <= (N << 1);++i)
		Get_Val(x), Get_Val(y), G::Add_Link(x, N + y, x + y);
	Fac[0] = 1;
	for (int i = 1;i <= (N << 1);++i)
		Fac[i] = Mult(Fac[i - 1], i);
	Inv[N << 1] = inverse(Fac[N << 1]);
	for (int i = (N << 1) - 1;i >= 0;--i)
		Inv[i] = Mult(Inv[i + 1], i + 1);
	G::work();
	return 0;
} 

Measured memory_bytes: 29696