#include <bits/stdc++.h>
using namespace std;

#define TRACE(x) x
#define WATCH(x) TRACE(cout << #x" = " << x << endl)
#define WATCHR(a, b) TRACE(for (auto it=a; it!=b;) cout << *(it++) << " "; cout << endl)
#define WATCHC(V) TRACE({cout << #V" = "; WATCHR(V.begin(), V.end());})

#define sz(x) int((x).size())
#define all(x) (x).begin(), (x).end()

using ll = long long;
using vi = vector<int>;
using vvi = vector<vi>;
using vll = vector<ll>;
using vvll = vector<vll>;
using vb = vector<bool>;
using vd = vector<double>;
using vs = vector<string>;

template<typename T> void ckmin(T& a, const T& b) { a = min(a, b); }
template<typename T> void ckmax(T& a, const T& b) { a = max(a, b); }

mt19937_64 rng(chrono::steady_clock::now().time_since_epoch().count());

struct union_find {
    vi P, R, S;

    union_find(int N) {
        P.resize(N), R.resize(N, 0), S.resize(N, 1);
        for (int i = 0; i < N; i++)
            P[i] = i;
    }

    int rep(int i) {
        if(P[i] != i) P[i] = rep(P[i]);
        return P[i];
    }

    bool unio(int a, int b) {
        a = rep(a), b = rep(b);
        if(a == b) return false;
        if(R[a] < R[b]) swap(a, b);
        P[b] = a;
        S[a] += S[b];
        if(R[a] == R[b]) R[a]++;
        return true;
    }
};

template<int MOD> struct modnum {
    int v;
    modnum() : v(0) {}
    modnum(ll _v) : v(_v % MOD) { if (v < 0) v += MOD; }
    explicit operator int() const { return v; }
    friend istream& operator >> (istream& i, modnum& n) { ll v; i >> v; n = modnum(v); return i; }
    friend ostream& operator << (ostream& o, const modnum& n) { return o << n.v; }

    friend bool operator == (const modnum& a, const modnum& b) { return a.v == b.v; }
    friend bool operator != (const modnum& a, const modnum& b) { return a.v != b.v; }

    modnum& operator += (const modnum& o) { v += o.v; if (v >= MOD) v -= MOD; return *this; }
    modnum& operator -= (const modnum& o) { v -= o.v; if (v < 0) v += MOD; return *this; }
    modnum& operator *= (const modnum& o) { v = int(ll(v) * ll(o.v) % MOD); return *this; }
    modnum operator - () { modnum res; if (v) res.v = MOD - v; return res; }
    friend modnum operator + (const modnum& a, const modnum& b) { return modnum(a) += b; }
    friend modnum operator - (const modnum& a, const modnum& b) { return modnum(a) -= b; }
    friend modnum operator * (const modnum& a, const modnum& b) { return modnum(a) *= b; }

    modnum pow(ll e) const {
        if (e == 0) return 1;
        if (e & 1) return *this * this->pow(e-1);
        return (*this * *this).pow(e/2);
    }

    modnum inv() const {
        int g = MOD, x = 0, y = 1;
        for (int r = v; r != 0; ) {
            int q = g / r;
            g %= r; swap(g, r);
            x -= q * y; swap(x, y);
        }

        assert(g == 1);
        assert(y == MOD || y == -MOD);
        return x < 0 ? x + MOD : x;
    }
    modnum& operator /= (const modnum& o) { return (*this) *= o.inv(); }
    friend modnum operator / (const modnum& a, const modnum& b) { return modnum(a) /= modnum(b); }

    static int totient() {
        int tot = MOD, tmp = MOD;
        for (int p = 2; p * p <= tmp; p++) if (tmp % p == 0) {
            tot = tot / p * (p - 1);
            while (tmp % p == 0) tmp /= p;
        }
        if (tmp > 1) tot = tot / tmp * (tmp - 1);
        return tot;
    }

    static int primitive_root() {
        if (MOD == 1) return 0;
        if (MOD == 2) return 1;

        int tot = totient(), tmp = tot;
        vi tot_pr;
        for (int p = 2; p * p <= tmp; p++) if (tot % p == 0) {
            tot_pr.push_back(p);
            while (tmp % p == 0) tmp /= p;
        }
        if (tmp > 1) tot_pr.push_back(tmp);

        for (int r = 2; r < MOD; r++) if (__gcd(r, MOD) == 1) {
            bool root = true;
            for (int p : tot_pr) root &= modnum(r).pow(tot / p) != 1;
            if (root) return r;
        }
        assert(false);
    }

    static modnum generator() { static modnum g = primitive_root(); return g; }
    static int discrete_log(modnum v) {
        static const int M = ceil(sqrt(MOD));
        static unordered_map<int, int> table;
        if (table.empty()) {
            modnum e = 1;
            for (int i = 0; i < M; i++) { table[e.v] = i; e *= generator(); }
        }
        static modnum f = generator().pow(totient() - M);

        for (int i = 0; i < M; i++) {
            if (table.count(v.v)) return table[v.v] + i * M;
            v *= f;
        }
        assert(false);
    }

    static modnum fact(int n) {
        static vector<modnum<MOD>> fact = { 1 };
        for (assert(n >= 0); fact.size() <= n; )
            fact.push_back(fact.back() * fact.size());
        return fact[n];
    }

    static modnum finv(int n) {
        static vector<modnum<MOD>> finv = { 1 };
        for (assert(n >= 0); finv.size() <= n; )
            finv.push_back(finv.back() / finv.size());
        return finv[n];
    }

    static modnum ncr(int n, int r) {
        if (r < 0 || n < r) return 0;
        return fact(n) * finv(r) * finv(n - r);
    }
};
using mn = modnum<int(1e9 + 7)>;
using vmn = vector<mn>;

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);

    int N;
    cin >> N;

    vvi adj(2 * N);
    union_find uf(2 * N);

    for (int i = 0, x, y; i < 2 * N; i++) {
        cin >> x >> y;
        uf.unio(--x, --y + N);
        adj[x].push_back(y + N);
        adj[y + N].push_back(x);
    }

    vvi comp(2 * N);
    for (int i = 0; i < 2 * N; i++) {
        comp[uf.rep(i)].push_back(i);
    }

    auto comb = [](const pair<int, mn>& a, const pair<int, mn>& b) -> pair<int, mn> {
        return { a.first + b.first,
            mn::ncr(a.first + b.first, a.first) * a.second * b.second };
    };

    pair<int, mn> ans = { 0, 1 };

    vi deg(2 * N), assign(2 * N, -1);
    for (const vi& grp : comp) if (sz(grp)) {
        vector<pair<int, int>> edges;
        for (int x : grp) {
            deg[x] = sz(adj[x]);
            if (x < N) for (int y : adj[x])
                edges.emplace_back(x, y);
        }
        if (sz(grp) != sz(edges)) { cerr << "Bad group\n"; ans.second = 0; break; }

        sort(all(edges));
        auto inx = [&](int x, int y) {
            if (x > y) swap(x, y);
            return lower_bound(all(edges), make_pair(x, y)) - edges.begin();
        };

        queue<int> leaf;
        for (int v : grp) if (deg[v] == 1) leaf.push(v);
        for (; !leaf.empty(); leaf.pop()) {
            int v = leaf.front();
            for (int u : adj[v]) if (deg[u] > 1) {
                assign[v] = inx(v, u);
                if (--deg[u] == 1)
                    leaf.push(u);
            }
        }

        auto go = [&](auto&& self, vvi& tree, int loc) -> pair<int, mn> {
            pair<int, mn> res = { 0, 1 };
            for (int ch : tree[loc])
                res = comb(res, self(self, tree, ch));
            res.first++;
            return res;
        };

        auto ct = [&](){
            vvi tree(sz(edges) + 1); vi par(sz(edges) + 1, -1);
            auto arc = [&](int p, int c) {
                tree[p].push_back(c);
                par[c] = p;
            };

            for (int v : grp) {
                int e = assign[v];
                int x, y; tie(x, y) = edges[e];
                if (v >= N) for (int xn : adj[v]) {
                    if (xn < x && edges[assign[xn]] == make_pair(xn, y))
                        arc(e, assign[xn]);
                }
                else for (int yn : adj[v]) {
                    if (yn < y && edges[assign[yn]] == make_pair(x, yn))
                        arc(e, assign[yn]);
                }
            }

            for (int i = 0; i < sz(edges); i++)
                if (par[i] == -1)
                    arc(sz(edges), i);
            return go(go, tree, sz(edges));
        };

        int cyc = -1;
        for (int v : grp) if (assign[v] == -1) {
            assert(deg[v] == 2);
            cyc = v;
        }

        pair<int, mn> tot = { sz(grp), 0 };
        for (int u : adj[cyc]) if (deg[u] == 2) {
            assign[cyc] = inx(cyc, u);
            for (int loc = u, prv = cyc; loc != cyc; ) {
                for (int nbr : adj[loc]) if (deg[nbr] == 2) {
                    assign[loc] = inx(loc, nbr);
                    if (assign[loc] == assign[prv]) continue;
                    prv = loc;
                    loc = nbr;
                    break;
                }
            }
            tot.second += ct().second;
        }
        ans = comb(ans, tot);
    }

    cout << ans.second << endl;

    return 0;
}



Measured memory_bytes: 34036