Address
Solution
将每个 和 分为一组,简单讨论一下不同情况下的限制。
若 都不为 , 是确定的,可以直接将这两个元素删去。
于是问题可以转化为:
给定一个图,图中有 个点,其中一些点有标记(原排列中不为 的所有元素),求图中有标记的点之间没有边相连的本质不同的完美匹配的方案数。
定义两组完美匹配本质相同当且仅当以下两个条件都成立:
- 对于每个有标记的点,两组匹配中的匹配点要么相同,要么编号都比该点大;
- 将所有不包含标记点的匹配按照顶点编号的最小值排序,得到的顶点编号的最小值的序列相同。
转化的问题中实际上是忽略了不包含标记点的匹配之间的顺序,实际情况中因为不包含标记点的匹配数是固定的,可以最后再计算。
考虑按编号从大到小安排匹配,设 表示已经处理了编号 的点,其中未匹配的有标记的点有 个、未匹配的没有标记的点有 个的方案数。
容易得到转移:
-
若 有标记,
-
若 没有标记,
设不包含标记点的匹配数为 ,最后的答案为 。
时间复杂度 。
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;
}