有一个 n×nn\times n 的矩阵,矩阵内有 2n2n 个球。对于 i[1,n]i \in [1,n](0,i)(i,0)(0,i) (i,0) 的位置各有一个启动后往右走/往上走的机器人,机器人撞到球会和球一起消失。问启动机器人顺序的方案数,满足所有球最后都消失。

n105n \le 10^{5}

ARC083F

Solution

把球看成点,机器人看成边。若存在一个(x,y)(x, y)的球,则连(x,y+n)(x, y + n),权值为x+yx + y的边

这样连边后,每选择一个点就会拿掉它相邻剩下的边权最小的边;拿掉一条边就代表这个机器人撞没了

并且必须要保证一个点对应一条边

因为只有nn个点nn条边,所以一定是一个基环树森林(否则无解)。显然,基环树的每个子树的方案是确定的(从叶子往根推,只有一种方案),并且环上也只有两种方案(每条边属于左边的点或是右边的点)。可以枚举这两种情况算答案

此时每个点已经分配了一个边权,但仍然有一些限制关系,即对于某个点分配到的那条边而言,所有与之相连且边权更小的边,必须要先选。显然,这样的限制又构成一棵森林

问题便转化为,求一个选择序列使得所有点的祖先比它自己先选择,是一个经典问题

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
#include <bits/stdc++.h>

#define x first
#define y second
#define y1 Y1
#define y2 Y2
#define mp make_pair
#define pb push_back
#define DEBUG(x) cout << #x << " = " << x << endl;

using namespace std;

typedef long long LL;
typedef pair <int, int> pii;

template <typename T> inline int Chkmax (T &a, T b) { return a < b ? a = b, 1 : 0; }
template <typename T> inline int Chkmin (T &a, T b) { return a > b ? a = b, 1 : 0; }
template <typename T> inline T read ()
{
T sum = 0, fl = 1; char ch = getchar();
for (; !isdigit(ch); ch = getchar()) if (ch == '-') fl = -1;
for (; isdigit(ch); ch = getchar()) sum = (sum << 3) + (sum << 1) + ch - '0';
return sum * fl;
}

inline void proc_status ()
{
ifstream t ("/proc/self/status");
cerr << string (istreambuf_iterator <char> (t), istreambuf_iterator <char> ()) << endl;
}

const int Maxn = 2e5 + 100;
const int Mod = 1e9 + 7;


namespace MATH
{
int fac[Maxn], ifac[Maxn];

inline void Add (int &a, int b) { if ((a += b) >= Mod) a -= Mod; }

inline int Pow (int a, int b)
{
int ans = 1;
for (int i = b; i; i >>= 1, a = (LL) a * a % Mod) if (i & 1) ans = (LL) ans * a % Mod;
return ans;
}

inline void init (int n = 2e5)
{
fac[0] = 1;
for (int i = 1; i <= n; ++i) fac[i] = (LL) fac[i - 1] * i % Mod;
ifac[n] = Pow (fac[n], Mod - 2);
for (int i = n - 1; i >= 0; --i) ifac[i] = (LL) ifac[i + 1] * (i + 1) % Mod;
}

inline int C (int n, int m) { if (n < m) return 0; return (LL) fac[n] * ifac[m] % Mod * ifac[n - m] % Mod; }
}

using namespace MATH;

int N;
int e, Begin[Maxn], Next[Maxn << 1], To[Maxn << 1], W[Maxn << 1];

inline void add_edge (int x, int y, int z) { To[++e] = y; Next[e] = Begin[x]; Begin[x] = e; W[e] = z; }

inline int merge (int a, int b) { return C (a + b, b); }

int Vis[Maxn], top, in_stk[Maxn];
pii stk[Maxn];

int found;
int circle[Maxn], in_circle[Maxn];
int val_cir[Maxn];

inline void dfs_circle (int x, int f, int w)
{
if (found) return ;
stk[++top] = mp (x, w), in_stk[x] = 1;

for (int i = Begin[x]; i; i = Next[i])
{
int y = To[i];
if (y == f) continue;
if (!in_stk[y]) dfs_circle (y, x, W[i]);
else
{
val_cir[++circle[0]] = W[i];
while (top)
{
int a = stk[top].x, b = stk[top].y; --top;
circle[circle[0]] = a;
in_circle[a] = 1;
val_cir[++circle[0]] = b;
if (a == y) break;
}
--circle[0];
found = 1;
return ;
}
if (found) return ;
}

--top, in_stk[x] = 0;
}

int val[Maxn], tree[Maxn];

inline void dfs_tree (int x, int f)
{
tree[++tree[0]] = x;
Vis[x] = 1;
for (int i = Begin[x]; i; i = Next[i])
{
int y = To[i];
if (y == f || in_circle[y]) continue;
val[y] = W[i];
dfs_tree (y, x);
}
}

int fa[Maxn], size[Maxn];

inline int dfs_calc (int x)
{
int ans = 1;
size[x] = 0;
for (int i = Begin[x]; i; i = Next[i])
{
int y = To[i];
if (x != fa[y]) continue;
int now = dfs_calc (y);
ans = (LL) ans * now % Mod * merge (size[x], size[y]) % Mod;
size[x] += size[y];
}
++size[x];
return ans;
}

inline int Calc ()
{
for (int t = 1; t <= tree[0]; ++t)
{
int x = tree[t];
fa[x] = size[x] = 0;
}

for (int t = 1; t <= tree[0]; ++t)
{
int x = tree[t];
for (int i = Begin[x]; i; i = Next[i])
{
int y = To[i];
if (W[i] < val[x]) fa[y] = x;
}
}

int ans = 1, sum_point = 0;
for (int t = 1; t <= tree[0]; ++t)
{
int x = tree[t];
if (!fa[x])
{
int now = dfs_calc (x);
ans = (LL) ans * now % Mod * merge (sum_point, size[x]) % Mod;
sum_point += size[x];
}
}

return ans;
}

inline int Process (int x)
{
found = circle[0] = top = 0;
dfs_circle (x, 0, 0);
tree[0] = 0;
for (int i = 1; i <= circle[0]; ++i) dfs_tree (circle[i], 0);

int ans = 0;
for (int i = 1; i <= circle[0]; ++i) val[circle[i]] = val_cir[i];
Add (ans, Calc ());

val_cir[circle[0] + 1] = val_cir[1];
for (int i = 1; i <= circle[0]; ++i) val[circle[i]] = val_cir[i + 1];
Add (ans, Calc ());

return ans;
}

int Choose[Maxn];

inline void Solve ()
{
int chosen = 0;
for (int i = 1; i <= 2 * N; ++i) if (Choose[i]) ++chosen;
if (chosen != 2 * N) return void (puts("0"));

int ans = 1, sum_point = 0;
for (int i = 1; i <= 2 * N; ++i)
if (!Vis[i])
{
int now = Process (i);
ans = (LL) ans * now % Mod * merge (sum_point, tree[0]) % Mod;
sum_point += tree[0];
}

cout << ans << endl;
}

inline void Input ()
{
N = read<int>();
for (int i = 1; i <= 2 * N; ++i)
{
int x = read<int>(), y = read<int>();
Choose[x] = Choose[y + N] = 1;
add_edge (x, y + N, x + y);
add_edge (y + N, x, x + y);
}
}

int main()
{

#ifdef hk_cnyali
freopen("F.in", "r", stdin);
freopen("F.out", "w", stdout);
#endif

MATH :: init ();
Input ();
Solve ();

return 0;
}