如果使用类似agc039E的方法,由于已经给定了一些边,并不容易讨论。
我们发现一个连通块事实上在圆上对应着一个区间
[
l
,
r
]
[l,r]
[l,r](
l
<
r
l<r
l<r),其中内部的点两两匹配,另外不存在
l
<
k
<
r
l<k<r
l<k<r,使得
[
l
,
k
]
[l,k]
[l,k]内部的点两两匹配。于是可以考虑对出度dp,枚举左端点
l
l
l,设
F
[
i
]
[
j
]
F[i][j]
F[i][j]表示当前考虑到
i
i
i,
[
l
,
i
]
[l,i]
[l,i]中有
j
j
j个点的匹配点在
(
i
,
2
N
]
(i,2N]
(i,2N]间,转移的时候不允许跟
[
1
,
i
)
[1,i)
[1,i)之间的点匹配。当
j
j
j第一次变为
0
0
0时,意味着出现了一个新的连通块,可以更新答案。
时间复杂度
O
(
N
3
)
\mathcal O(N^3)
O(N3)。
#include <bits/stdc++.h>
#define MOD 1000000007
using namespace std;
typedef long long ll;
inline void add(int &x,ll y) {
x=(x+y)%MOD;
}
ll facd[305];
void pre(int n) {
facd[0]=1;
for(int i=1;i<=n;i++) facd[i]=facd[i-1]*(2LL*i-1)%MOD;
}
int num[605];
int f[2][305];
int main() {
int n,m;
scanf("%d%d",&n,&m);
pre(n);
for(int i=1;i<=m;i++) {
int x,y;
scanf("%d%d",&x,&y);
num[x]=y;
num[y]=x;
}
int ans=0;
for(int i=1;i<=2*n;i++) {
int cur=0;
memset(f[cur],0,sizeof(f[cur]));
f[cur][0]=1;
int sz1=0,sz2=0;
for(int j=i;j<=2*n;j++) {
if (num[j]) {
if (num[j]>j) sz1++;
else if (num[j]<i) break;
else sz1--;
}
else {
sz2++;
cur^=1;
memset(f[cur],0,sizeof(f[cur]));
for(int k=0;k<=min(n,sz2);k++)
if (f[cur^1][k]) {
if (k) add(f[cur][k-1],(ll)f[cur^1][k]*k);
add(f[cur][k+1],f[cur^1][k]);
}
}
if (!sz1&&f[cur][0]) {
add(ans,(ll)f[cur][0]*facd[n-m-(sz2>>1)]);
f[cur][0]=0;
}
}
}
printf("%d\n",ans);
return 0;
}