可以发现,如果把环断开成为一条链,那么线段的交实际上就类似于区间的交。
所以,我们可以考虑用连通块 S S S 中涉及到的编号最小的点 L L L 和编号最大的点 R R R 所构成的区间 [ L , R ] [L, R] [L,R] 来表示 S S S。
下文我们称 [ L , R ] [L, R] [L,R] 为连通块 S S S 的表示区间。
那么对于一个已经连好边的图,我们可以发现一个性质:
- 对于两个不同的连通块,它们的表示区间要么是包含关系,要么交集为空。
这个性质是有利于统计的,因为我们不必考虑方案中产生的交区间对不同连通块的影响了。
具体地,我们可以考虑设 f i , j f_{i, j} fi,j 为构造出表示区间是 [ i , j ] [i, j] [i,j] 的连通块的方案数,那么这个连通块对答案的贡献是: f i , j f_{i, j} fi,j 和在区间 [ i , j ] [i, j] [i,j] 外的其它点任意连接的方案数的乘积。
这个“在区间 [ i , j ] [i, j] [i,j] 外的其它点任意连接的方案数”该怎么计算呢?
我们的方法是,预处理两个数组: g i g_i gi 和 c n t i , j cnt_{i, j} cnti,j:
g i g_i gi 表示 i i i 个点之间任意连接的方案数;
c n t i , j cnt_{i, j} cnti,j 表示区间 [ i , j ] [i, j] [i,j] 内没有连接的点的个数,即没有在给定的 k k k 条线段中出现过。
那么,“在区间 [ i , j ] [i, j] [i,j] 外的其它点任意连接的方案数”即为:
g ( n − k ) ∗ 2 − c n t i , j g_{(n - k) * 2 - cnt_{i, j}} g(n−k)∗2−cnti,j
接下来我们考虑怎样预处理这两个数组。
g i g_i gi 数组:当 i i i 为奇数时,结果为 0 0 0(奇数个点无法完整地相连),否则为 g i − 2 ∗ ( i − 1 ) g_{i - 2} * (i - 1) gi−2∗(i−1)。
我们设 g 0 g_0 g0 为 1 1 1,那么 g g g 的通项公式平凡,这里就留给读者了,时间复杂度 O ( n ) O(n) O(n) 。
c n t i , j cnt_{i, j} cnti,j 数组:这个就不用多说了, O ( n 2 ) O(n^2) O(n2) 处理即可。
最后我们考虑如何计算 f i , j f_{i, j} fi,j,不过前提是要保证 f i , j f_{i, j} fi,j 的存在性和独立性。
首先我们需要保证区间 [ i , j ] [i, j] [i,j] 中的所有连边不超出区间的范围,这样才能保证 f i , j f_{i, j} fi,j 的存在性,如果上述条件无法满足,那么令 f i , j = 0 f_{i, j} = 0 fi,j=0 。
如果不容斥,那么 f i , j f_{i, j} fi,j 就是 g c n t i , j g_{cnt_{i, j}} gcnti,j,但是显然需要容斥,因为区间 [ i , j ] [i, j] [i,j] 内部可能会出现不连通的情况。
我们考虑枚举一个 k ∈ [ i + 1 , j − 1 ] k \in [i + 1, j - 1] k∈[i+1,j−1],令区间 [ i , k ] [i, k] [i,k] 和 [ k + 1 , j ] [k + 1, j] [k+1,j] 独立,由于在算 f i , k f_{i, k} fi,k 时已经保证了其独立性,所以这样不合法的方案有 f i , k ∗ g c n t k + 1 , j f_{i, k} * g_{cnt_{k + 1, j}} fi,k∗gcntk+1,j 种。
所以 f i , j f_{i, j} fi,j 的计算公式为:
f i , j = g c n t i , j − ∑ k = i + 1 j − 1 f i , k ∗ g c n t k + 1 , j f_{i, j} = g_{cnt_{i, j}} - \sum_{k = i + 1}^{j - 1}f_{i, k} * g_{cnt_{k + 1, j}} fi,j=gcnti,j−k=i+1∑j−1fi,k∗gcntk+1,j
算法到这里就大功告成了,实现的细节具体看代码吧。
代码:
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cassert>
#define FILE
#define MAXN 1005
#define MOD 1000000007
using namespace std;
int n, k, Match[MAXN], cnt[MAXN][MAXN], g[MAXN], f[MAXN][MAXN], ans;
bool Matched[MAXN];
int main()
{
#ifdef FILE
freopen("Input.in", "r", stdin);
freopen("Output.out", "w", stdout);
#endif
scanf("%d%d", &n, &k);
for(register int i = 1; i <= k; i++)
{
int u, v; scanf("%d%d", &u, &v);
Matched[u] = Matched[v] = true;
Match[u] = v, Match[v] = u;
}
for(register int i = 1; i <= n * 2; i++) for(register int j = i; j <= n * 2; j++) cnt[i][j] = cnt[i][j - 1] + !Matched[j];
g[0] = 1; for(register int i = 2; i <= n * 2; i += 2) g[i] = 1LL * g[i - 2] * (i - 1) % MOD;
for(register int i = 1; i <= n * 2; i++)
{
for(register int j = i; j <= n * 2; j++)
{
bool Flag = false;
for(register int k = i; k <= j; k++) Flag |= (Matched[k] && (Match[k] < i || Match[k] > j));
if(Flag) continue;
f[i][j] = g[cnt[i][j]];
for(register int k = i + 1; k <= j - 1; k++) f[i][j] = (f[i][j] - 1LL * f[i][k] * g[cnt[k + 1][j]] % MOD + MOD) % MOD;
}
}
for(register int i = 1; i <= n * 2; i++) for(register int j = i; j <= n * 2; j++) ans = (ans + 1LL * f[i][j] * g[(n - k) * 2 - cnt[i][j]] ) % MOD;
printf("%d\n", ans);
return 0;
}