考完试才发现E是简单题。。。
思考一下怎么样是合法的生成树。不妨设
1
1
1号点跟
k
k
k号点匹配,那么对于区间
[
2
,
k
−
1
]
[2,k-1]
[2,k−1]和
[
k
+
1
,
n
]
[k+1,n]
[k+1,n],我们会各自按顺序分成
c
c
c段,每一段内部有奇数个点,其中其他点内部匹配,伸出一个点跨越中间的边跟另一边的一段伸出的一个点匹配,并且匹配的段是按跟
1
1
1号点距离一一对应的。
那么看起来就可以DP了,我们设
F
[
i
]
[
j
]
[
k
]
F[i][j][k]
F[i][j][k]表示区间
[
i
,
j
]
[i,j]
[i,j](长度为奇数)内部其他点两两匹配,伸出的点为
k
k
k(其他匹配要跟
k
k
k伸出的边连通)的方案数,转移的时候直接枚举
i
i
i和
j
j
j所在的段的长度和伸出的点即可得到一个多项式做法。
在转移的同时维护
G
[
i
]
[
j
]
[
k
]
G[i][j][k]
G[i][j][k]表示区间外一点
k
k
k跟区间
[
i
,
j
]
[i,j]
[i,j]内部某点匹配,
[
i
,
j
]
[i,j]
[i,j]内部其他点两两匹配(同样要连通)的方案数,就可以加速转移了。时间复杂度是
O
(
n
5
)
\mathcal O(n^5)
O(n5),题解给出了吓人(不知道是啥)的
O
(
n
8
)
\mathcal O(n^8)
O(n8)做法。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
char str[45][45];
ll f[45][45][45],g[45][45][45];
int main() {
int n;
scanf("%d",&n);
n=2*n;
for(int i=1;i<=n;i++) scanf("%s",str[i]+1);
for(int i=1;i<n;i++) {
f[i][i][i]=1;
for(int j=i+1;j<=n;j++)
if (str[i][j]=='1') g[i][i][j]=1;
}
for(int i=3;i<n;i+=2)
for(int j=1;i+j<=n;j++) {
int k=i+j-1;
for(int l=j;l<=k;l+=2)
for(int r=k;r>l;r-=2) {
ll s=0;
for(int t=r;t<=k;t++) s+=g[j][l][t]*f[r][k][t];
for(int t=l+1;t<r;t++) f[j][k][t]+=f[l+1][r-1][t]*s;
}
for(int t1=j+1;t1<k;t1++)
for(int t2=k+1;t2<n;t2++)
if (str[t1][t2]=='1') g[j][k][t2]+=f[j][k][t1];
}
ll ans=0;
for(int i=1;i<n;i++)
if (str[i][n]=='1') ans+=f[1][n-1][i];
printf("%lld\n",ans);
return 0;
}