#include <bits/stdc++.h>
#include <ext/pb_ds/tree_policy.hpp>
#include <ext/pb_ds/assoc_container.hpp>

using namespace std;
using namespace __gnu_pbds;
 
typedef long long ll;
typedef vector<int> vi;
typedef pair<int, int> pii;
template <class T> using Tree = tree<T, null_type, less<T>, rb_tree_tag,tree_order_statistics_node_update>;

#define FOR(i, a, b) for (int i=a; i<(b); i++)
#define F0R(i, a) for (int i=0; i<(a); i++)
#define FORd(i,a,b) for (int i = (b)-1; i >= a; i--)
#define F0Rd(i,a) for (int i = (a)-1; i >= 0; i--)

#define sz(x) (int)(x).size()
#define mp make_pair
#define pb push_back
#define f first
#define s second
#define lb lower_bound
#define ub upper_bound

const int MOD = 1000000007;
const int MX = 200001;

int N, par[MX], depth[MX], visit[MX], cyc[MX]; // parent, depth, visited, cycle
int done[MX], sz[MX], num[MX];
ll fac[MX], in[MX], ans = 1;
vi verts, edges, cy, adj1[MX];
int nex = 1;
vector<pii> cur, adj0[MX];

// we need to create a separate tree for the edges relating to each other

/////////// OK
void dfs1(int ind, int pre) {
    par[ind] = pre; visit[ind] = 1; verts.pb(ind);
    for (pii i: adj0[ind]) if (!visit[i.f]) {
        depth[i.f] = depth[ind]+1;
        dfs1(i.f,ind);
    }
}

void dfs2(int ind, int pre) {
    int ii = 0; 
    while (adj0[ind][ii].f != pre) ii++;
    //cout << "HI " << ind+1 << " " << pre+1 << " " << ii << "\n";
    for (pii x: adj0[ind]) if (x.f < pre) {
        adj1[adj0[ind][ii].s].pb(x.s);
        //cout << "OH " << x.f+1 << " " << ind+1 << " " << pre+1 << "\n";
    }
    for (pii x: adj0[ind]) if (x.f != pre) dfs2(x.f,ind);
}

////////// OK
ll inv (ll b) {
    ll p = MOD-2, ans = 1;
    while (p) {
        if (p&1) ans = (ans*b) % MOD;
        b = (b*b) % MOD;
        p /= 2;
    }
    return ans;
}

////////// OK
ll get(int x) {
    if (done[x]) return sz[x];
    sz[x] = done[x] = 1;
    for (int i: adj1[x]) sz[x] += get(i);
    return sz[x];
}

ll process() {
    ll ret = 1;
    for (int i: edges) ret = (ret*in[get(i)]) % MOD;
    return ret;
}

// account for a possible cycle?

ll test1() {
    for (int i: edges) {
        adj1[i].clear();
        done[i] = 0;
    }
    F0R(i,cy.size()) {
        int v = cy[i], ind = 0; 
        while (adj0[v][ind].f != cy[(i+1)%cy.size()]) ind++;
        
        for (pii j: adj0[v]) if (j.f < cy[(i+1)%cy.size()]) 
            adj1[adj0[v][ind].s].pb(j.s);
            
        for (pii j: adj0[v]) if (!cyc[j.f]) dfs2(j.f,v);
    }
    return process();
}

ll test2() {
    for (int i: edges) {
        adj1[i].clear();
        done[i] = 0;
    }
    F0R(i,cy.size()) {
        int v = cy[i], ind = 0;
        while (adj0[v][ind].f != cy[(i+cy.size()-1)%cy.size()]) ind++;
        
        for (pii j: adj0[v]) if (j.f < cy[(i+cy.size()-1)%cy.size()]) 
            adj1[adj0[v][ind].s].pb(j.s);
        
        for (pii j: adj0[v]) if (!cyc[j.f]) dfs2(j.f,v);
    }
    return process();
}

////////// OK
void solve(int ind) {
    cur.clear(), verts.clear(), edges.clear(), cy.clear(); 
    dfs1(ind,-1); 
    
    for (int x: verts) for (pii y: adj0[x]) if (depth[x] < depth[y.f]) {
        edges.pb(y.s);
        if(par[y.f] != x) cur.pb({x,y.f});
    }
    
    if (cur.size() != 1) { cout << 0; exit(0); }
    while (cur[0].s != cur[0].f) { // elements of the cycle!
        cy.pb(cur[0].s);
        cyc[cur[0].s] = 1;
        cur[0].s = par[cur[0].s];
    }
    cyc[cur[0].s] = 1;
    cy.pb(cur[0].s);
    
    ll ret = (test2()+test1()) % MOD;
    ans = (ans*ret) % MOD;
}

////////// OK
void init() {
	ios_base::sync_with_stdio(0);cin.tie(0);
	cin >> N;
	fac[0] = in[0] = 1;
	FOR(i,1,2*N+1) {
	    fac[i] = (i*fac[i-1]) % MOD;
	    in[i] = inv(i) % MOD;
	}
	ans = fac[2*N];
	F0R(i,2*N) {
	    int x,y; cin >> x >> y; x--, y--;
	    adj0[x].pb({y+N,i});
	    adj0[y+N].pb({x,i});
	}
}

////////// OK
int main() {
    init();
	F0R(i,2*N) if (!visit[i]) solve(i);
	cout << ans;
}

// read!
// ll vs. int!

Measured memory_bytes: 33140