我们把位置在\((2i - 1, 2i)\)的两个点叫做一对点。

显然如果这对点两个都被限定了直接丢掉完事。

如果有一个没有被限定就先留下来。

注意到其他的形如\((-1, -1)\)的顺序是可以随便变化的, 所以先不考虑, 最后乘上一个阶乘就可以了。

考虑用\(f(i, j, k)\)表示当前填第\(i\)个数, 剩下\(j\)\((-1, d)\)对, 这种对是自己搞出来的, 还剩下\(k\)\((-1, e)\)对, 是被限定了的。

从这里可以发现, 我们的\(i\)应该从大到小枚举, 这样才可以避免后效性, 否则前面选了一个数, 后面就没法改变这一对的值, 从而出现后效性。

然后就直接分类讨论\(dp\)就好了。

#include <cmath>
#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
#include <iostream>

using namespace std;

#define R register
const int N = 600 + 5;
const int P = 1e9 + 7;

inline int read() {
	int x = 0, f = 1; char a = getchar();
	for(; a > '9' || a < '0'; a = getchar()) if(a == '-') f = -1;
	for(; a >= '0' && a <= '9'; a = getchar()) x = x * 10 + a - '0';
	return x * f;
}

int n;
int a[N], ok[N], vis[N];
int f[N][N][N];
int S[N];
int tot1 = 0, tot2 = 0;

int main() {
	#ifdef IN
	freopen("a.in", "r", stdin);
	//freopen(".out", "w", stdout);
	#endif
	n = read(); n <<= 1;
	for(R int i = 1; i <= n; i ++) a[i] = read();
	for(R int i = 1; i <= n; i += 2) {
		if(a[i] > 0 && a[i + 1] > 0) {
			vis[a[i]] = 1;
			vis[a[i + 1]] = 1;
			continue;
		}
		else {
			if(a[i] == -1 && a[i + 1] == -1) tot1 ++;
			else {
				tot2 ++;
				if(a[i + 1] == -1) ok[a[i]] = 1;
				else ok[a[i + 1]] = 1; 		
			}
		}
	}
	int m = 0;
	for(R int i = n; i >= 1; i --) if(vis[i] == 0) S[++ m] = i;
	f[0][0][0] = 1;
	for(R int i = 1; i <= m; i ++) 
		for(R int j = 0; j <= n; j ++)
			for(R int k = 0; k <= n; k ++) {
				if(! ok[S[i]]) {
					if(j) f[i][j][k] = (f[i][j][k] + f[i - 1][j - 1][k]) % P;
					f[i][j][k] = (f[i][j][k] + f[i - 1][j + 1][k]) % P;
					f[i][j][k] = (f[i][j][k] + 1LL * f[i - 1][j][k + 1] * (k + 1) % P) % P;
				}
				else {
					f[i][j][k + 1] = (f[i][j][k + 1] + f[i - 1][j][k]) % P;
					if(j) f[i][j - 1][k] = (f[i][j - 1][k] + f[i - 1][j][k]) % P;
				}
			}
	int ans = f[m][0][0];
	for(R int i = 1; i <= tot1; i ++) ans = 1LL * ans * i % P;
	printf("%d\n", ans);
	return 0;
}