考虑\(1\)号点向外连出一条边之后, 整个圆被分成两个部分, 每个部分肯定是内部连边然后一个点连一条边出来跨越一号点连出的那一条线。考虑对这种形状的部分进行\(dp\)。
设\(f(i, j, k)\)表示\([i, j]\)这个区间内部匹配, \(k\)点向外连接的方案数,
\(g(i,j,k)\)表示外面一点\(k\)连接到区间\([i, j]\)内, 区间内其他点两两匹配的方案数目。
考虑转移, 可以枚举左边和右边的小块, 然后连接, 再枚举中间的每一个点作为连出去的点转移, 因为只有左右两边连起来, 中间连出去才会是一棵树, 所以连出去的点除了一个点的情况, 其他时候都不该在区间边上。
比如枚举了当前要处理的块的左右端点\(j, k\)。
设\(s = \sum g(j, l, t) * f(r, k, t)\),表示两边的连通块连起来的总方案数目。
\(l, r\)是枚举的中间的那个区间的左右端点。
然后枚举中间区间的每一个点作为连出的点, 有:
\(f(j, k, t) = \sum f(l + 1, r - 1, t)\)
最后更新一下\(g\), 枚举在外面的点和在里面的点更新即可。
#include <cmath>
#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
#include <iostream>
using namespace std;
#define R register
#define LL long long
const int N = 50;
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;
}
char s[N][N];
LL f[N][N][N], g[N][N][N];
int main() {
#ifdef IN
freopen("a.in", "r", stdin);
//freopen(".out", "w", stdout);
#endif
int n = read(); n <<= 1;
for(R int i = 1; i <= n; i ++)
scanf("%s", s[i] + 1);
for(R int i = 1; i < n; i ++) {
f[i][i][i] = 1;
for(R int j = i + 1; j <= n; j ++)
if(s[i][j] == '1')
g[i][i][j] = 1;
}
for(R int len = 3; len < n; len += 2) {
for(R int j = 1; j + len <= n; j ++) {
int k = len + j - 1;
for(R int l = j; l <= k; l += 2)
for(R int r = k; r > l; r -= 2) {
LL s = 0;
for(R int t = r; t <= k; t ++)
s += g[j][l][t] * f[r][k][t];
for(R int t = l + 1 ; t < r; t ++)
f[j][k][t] += f[l + 1][r - 1][t] * s;
}
for(R int t1 = j + 1; t1 < k; t1 ++)
for(R int t2 = k + 1; t2 < n; t2 ++)
if(s[t1][t2] == '1')
g[j][k][t2] += f[j][k][t1];
}
}
LL ans = 0;
for(R int i = 1; i < n; i ++)
if(s[i][n] == '1') ans += f[1][n - 1][i];
printf("%lld\n", ans);
return 0;
}