Address
Solution
将每个 A 2 i − 1 A_{2i - 1} A2i−1 和 A 2 i A_{2i} A2i 分为一组,简单讨论一下不同情况下的限制。
若 A 2 i − 1 , A 2 i A_{2i - 1},A_{2i} A2i−1,A2i 都不为 − 1 -1 −1, B i B_i Bi 是确定的,可以直接将这两个元素删去。
于是问题可以转化为:
给定一个图,图中有 2 N 2N 2N 个点,其中一些点有标记(原排列中不为 − 1 -1 −1 的所有元素),求图中有标记的点之间没有边相连的本质不同的完美匹配的方案数。
定义两组完美匹配本质相同当且仅当以下两个条件都成立:
- 对于每个有标记的点,两组匹配中的匹配点要么相同,要么编号都比该点大;
- 将所有不包含标记点的匹配按照顶点编号的最小值排序,得到的顶点编号的最小值的序列相同。
转化的问题中实际上是忽略了不包含标记点的匹配之间的顺序,实际情况中因为不包含标记点的匹配数是固定的,可以最后再计算。
考虑按编号从大到小安排匹配,设 f i , j , k f_{i,j,k} fi,j,k 表示已经处理了编号 ≥ i \ge i ≥i 的点,其中未匹配的有标记的点有 j j j 个、未匹配的没有标记的点有 k k k 个的方案数。
容易得到转移:
-
若 i i i 有标记,
f i , j , k = f i + 1 , j − 1 , k + f i + 1 , j , k + 1 \begin{aligned} f_{i,j,k} = f_{i + 1,j - 1,k} + f_{i + 1,j,k+1} \end{aligned} fi,j,k=fi+1,j−1,k+fi+1,j,k+1 -
若 i i i 没有标记,
f i , j , k = f i + 1 , j , k − 1 + ( j + 1 ) f i + 1 , j + 1 , k + f i + 1 , j , k + 1 \begin{aligned}f_{i,j,k} = f_{i + 1, j, k - 1} + (j+1)f_{i + 1, j + 1, k} + f_{i + 1, j, k+1}\end{aligned} fi,j,k=fi+1,j,k−1+(j+1)fi+1,j+1,k+fi+1,j,k+1
设不包含标记点的匹配数为 c n t cnt cnt,最后的答案为 c n t ! f 1 , 0 , 0 cnt!f_{1,0,0} cnt!f1,0,0。
时间复杂度 O ( n 3 ) \mathcal O(n^3) O(n3)。
Code
#include <bits/stdc++.h>
template <class T>
inline void read(T &res)
{
char ch; bool flag = false; res = 0;
while (ch = getchar(), !isdigit(ch) && ch != '-');
ch == '-' ? flag = true : res = ch ^ 48;
while (ch = getchar(), isdigit(ch))
res = res * 10 + ch - 48;
flag ? res = -res : 0;
}
const int N = 305;
const int M = 605;
const int mod = 1e9 + 7;
int s1[M], s2[M], a[M], b[M], f[2][N][M];
bool tag[M], del[M];
int n, m, cnt;
inline void add(int &x, int y)
{
x += y;
x >= mod ? x -= mod : 0;
}
inline void Delete(int x)
{
del[x] = true;
for (int i = 1; i <= m; ++i)
if (!del[i] && a[i] > a[x])
--a[i];
}
int main()
{
read(n);
m = n << 1;
for (int i = 1; i <= m; ++i)
read(a[i]);
for (int i = 1; i <= m; i += 2)
if (~a[i] && ~a[i + 1])
{
Delete(i);
Delete(i + 1);
}
n = 0;
for (int i = 1; i <= m; i += 2)
if (!del[i])
{
if (~a[i])
tag[a[i]] = true;
if (~a[i + 1])
tag[a[i + 1]] = true;
if (a[i] == -1 && a[i + 1] == -1)
++cnt;
++n;
}
m = n << 1;
f[m + 1 & 1][0][0] = 1;
for (int i = m; i >= 1; --i)
{
int now = i & 1, lst = now ^ 1;
s1[i] = s1[i + 1] + tag[i];
s2[i] = s2[i + 1] + !tag[i];
if (tag[i])
{
for (int j = 0; j <= s1[i]; ++j)
for (int k = 0; k <= s2[i]; ++k)
{
f[now][j][k] = j > 0 ? f[lst][j - 1][k] : 0;
add(f[now][j][k], f[lst][j][k + 1]);
}
}
else
{
for (int j = 0; j <= s1[i]; ++j)
for (int k = 0; k <= s2[i]; ++k)
f[now][j][k] = (1ll * f[lst][j + 1][k] * (j + 1)
+ (k > 0 ? f[lst][j][k - 1] : 0) + f[lst][j][k + 1]) % mod;
}
}
int res = f[1][0][0];
for (int i = 1; i <= cnt; ++i)
res = 1ll * res * i % mod;
printf("%d\n", res);
return 0;
}