Description
有一个 \(n\times n\) 的矩阵,矩阵内有 \(2n\) 个球。对于 \(i \in [1,n]\) ,\((0,i) (i,0)\) 的位置各有一个启动后往右走/往上走的机器人,机器人撞到球会和球一起消失。问启动机器人顺序的方案数,满足所有球最后都消失。
\(n \le 10^{5}\)
Solution
先建图,对于平面上的一个 \((x, y)\) 位置的球,把机器人看做点,球看做边,连一条 \(x\) 到 \(y + n\) ,权值为 \(x + y\) 的边。
然后问题就转化成了在这张图上排列点的操作顺序使最后所有边都被删除,一个点操作是指将和它相连的边中最小的一条边删除。
不难发现这个图是一个基环树森林,那么不在环上的点每个点要删除的边是固定的,在环上的点就有两种选择,每个点顺时针和逆时针删。
我们可以进一步构造模型,现在每个点都有一个要删除的边,把每个点u要删除边权记为val[u],定义为u的点权,那么在原图的基础上再建一个新图:每个点向和他连边的点中权值小于它自己权值的点连新边,这样建出来的新图是原图边集的一个子集,并且是一个森林,现在问题又转换成求每个点的父亲要比自己先被操作的方案数,这就是一个很经典的问题了,dfs求出size计数即可,大概写一下转移。
假设有3个儿子 \(v_1, v_2, v_3\)。
\[ f[u] = {size[v_1] + size[v_2]\choose size[v_1]} {size[v_1] + size[v_2] + size[v_3]\choose size[v_1] + size[v_2]} \times f[v_2]\times f[v_3] \times f[v_3] \]
Code
#include <iostream>
#include <queue>
#include <vector>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <fstream>
typedef long long LL;
typedef unsigned long long uLL;
#define PII pair<int, int>
#define SZ(x) ((int)x.size())
#define ALL(x) (x).begin(), (x).end()
#define MP(x, y) std::make_pair(x, y)
#define DE(x) cerr << x << endl;
#define debug(...) fprintf(stderr, __VA_ARGS__)
#define GO cerr << "GO" << endl;
#define rep(i, a, b) for (register int (i) = (a); (i) <= (b); ++(i))
using namespace std;
inline void proc_status()
{
ifstream t("/proc/self/status");
cerr << string(istreambuf_iterator<char>(t), istreambuf_iterator<char>()) << endl;
}
inline int read()
{
register int x = 0; register int f = 1; register char c;
while (!isdigit(c = getchar())) if (c == '-') f = -1;
while (x = (x << 1) + (x << 3) + (c xor 48), isdigit(c = getchar()));
return x * f;
}
template<class T> inline void write(T x)
{
static char stk[30]; static int top = 0;
if (x < 0) { x = -x, putchar('-'); }
while (stk[++top] = x % 10 xor 48, x /= 10, x);
while (putchar(stk[top--]), top);
}
template<typename T> inline bool chkmin(T &a, T b) { return a > b ? a = b, 1 : 0; }
template<typename T> inline bool chkmax(T &a, T b) { return a < b ? a = b, 1 : 0; }
const int maxN = (int) 2e5;
const int mod = (int) 1e9 + 7;
struct Edge
{
int v, w;
Edge() { }
Edge(int v, int w) : v(v), w(w) { }
} ;
int n;
bool vis[maxN + 2], instk[maxN + 2], found, in_cir[maxN + 2], choose[maxN + 2];
int top, val[maxN + 2], par[maxN + 2], size[maxN + 2];
PII stk[maxN + 2];
vector<pair<int, int> > circle;
vector<int> node;
vector<Edge> g[maxN + 2];
namespace math
{
int fac[2 * maxN + 2], ifac[2 * maxN + 2];
LL qpow(LL a, LL b)
{
LL ans = 1;
while (b)
{
if (b & 1)
ans = ans * a % mod;
a = a * a % mod;
b >>= 1;
}
return ans;
}
void init()
{
fac[0] = 1;
for (int i = 1; i <= 2 * n; ++i) fac[i] = 1ll * fac[i - 1] * i % mod;
ifac[2 * n] = qpow(fac[2 * n], mod - 2);
for (int i = 2 * n - 1; i >= 0; --i) ifac[i] = 1ll * ifac[i + 1] * (i + 1) % mod;
}
int C(int n, int m) { return 1ll * fac[n] * ifac[m] % mod * ifac[n - m] % mod; }
int merge(int a, int b) { return C(a + b, a); }
}
using namespace math;
void input()
{
n = read() << 1;
for (int i = 1; i <= n; ++i)
{
int x = read(), y = read();
g[x].emplace_back(y + n / 2, x + y);
g[y + n / 2].emplace_back(x, x + y);
choose[x] = choose[y + n / 2] = 1;
}
}
void dfs_circle(int x, int fa, int edge)
{
if (found) return;
stk[++top] = MP(x, edge);
instk[x] = 1;
for (Edge E : g[x])
{
int v = E.v, w = E.w;
if (v == fa) continue;
if (!instk[v])
{
dfs_circle(v, x, w);
}
else
{
int To = v;
do
{
v = stk[top].first;
in_cir[v] = 1;
circle.push_back(stk[top--]);
} while (v != To);
circle.back().second = w;
found = 1;
return;
}
if (found) return;
}
instk[x] = 0;
top--;
}
void dfs1(int u, int fa)
{
vis[u] = 1;
node.push_back(u);
for (Edge E : g[u])
{
int v = E.v, w = E.w;
if (v != fa and !in_cir[v])
{
val[v] = w;
dfs1(v, u);
}
}
}
int dfs2(int u)
{
int ans = 1;
size[u] = 0;
for (Edge E : g[u])
{
int v = E.v;
if (par[v] != u) continue;
int cur = dfs2(v);
ans = 1ll * ans * cur % mod * merge(size[u], size[v]) % mod;
size[u] += size[v];
}
size[u]++;
return ans;
}
int calc()
{
for (int u : node)
{
size[u] = 0;
par[u] = 0;
}
for (int u : node)
{
for (Edge E : g[u])
{
int v = E.v, w = E.w;
if (w < val[u]) par[v] = u;
}
}
int SIZE = 0, ans = 1;
for (int u : node)
{
if (!par[u])
{
int cur = dfs2(u);
ans = 1ll * ans * cur % mod * merge(SIZE, size[u]) % mod;
SIZE += size[u];
}
}
return ans;
}
void solve()
{
int cnt = 0;
for (int i = 1; i <= n; ++i)
cnt += choose[i];
if (cnt != n)
{
cout << 0 << endl;
return;
}
int SIZE = 0, ans = 1;
for (int i = 1; i <= n && ans; ++i)
{
if (!vis[i])
{
int cur = 0;
top = 0;
found = 0;
circle.clear();
dfs_circle(i, 0, 0);
node.clear();
for (int j = 0; j < SZ(circle); ++j)
dfs1(circle[j].first, 0);
for (int j = 0; j < SZ(circle); ++j)
val[circle[j].first] = circle[j].second;
(cur += calc()) %= mod;
circle.push_back(circle[0]);
for (int j = 1; j < SZ(circle); ++j)
val[circle[j].first] = circle[j - 1].second;
(cur += calc()) %= mod;
ans = 1ll * ans * cur % mod * merge(SIZE, node.size()) % mod;
SIZE += node.size();
}
}
printf("%d\n", ans);
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("AGC083F.in", "r", stdin);
freopen("AGC083F.out", "w", stdout);
#endif
input();
math::init();
solve();
return 0;
}