题目链接
题意:
在一个环上有2n个点,按顺时针编号,你需要将这些点两两配对相连,形成若干个连通块。连通的含义是只要连接的两个点能通过它配对点的直线,经过与其他线段的交点能到达另一条线。现在已经有k对点配对好了,告诉你这k对点的编号,问你剩下的n-k对的所有配对方法最后形成的连通块数量之和。对1e9+7取模。
题解:
我还是水平好菜的,遇到一些有难度的计数题还是毫无思路啊。
这题考虑dp,我们设dp[i][j]为i与j连通,并且连通块内最小编号是i,最大编号是j的方案数,那么它对答案的贡献就是
d
p
[
i
]
[
j
]
∗
dp[i][j]*
dp[i][j]∗其他没连的随便连的方案数。我们先
n
2
n^2
n2统计出
i
i
i到
j
j
j的中间有多少个点没有连过,记为
c
n
t
[
[
i
]
[
j
]
cnt[[i][j]
cnt[[i][j],然后考虑如何计算dp数组。我们发现直接算并不好算,我们可以用
i
i
i到
j
j
j中的数随便连的方案数减去其中没有让
i
i
i和
j
j
j连通的方案数。我们预处理出
i
i
i个点随便连的方案数
g
[
i
]
g[i]
g[i],对于
i
i
i是奇数,答案是0,对于偶数
x
x
x,答案是
∏
i
=
1
x
−
1
i
\prod_{i=1}^{x-1}i
∏i=1x−1i。那么对于
i
i
i到
j
j
j之间没有连到
i
i
i和
j
j
j以外的边的情况(连出去最小数和最大数就不是
i
i
i和
j
j
j了),我们有
d
p
[
i
]
[
j
]
=
g
[
i
]
−
∑
k
=
i
+
1
j
−
1
d
p
[
i
]
[
k
]
∗
g
[
c
n
t
[
i
]
[
j
]
]
dp[i][j]=g[i]-\sum_{k=i+1}^{j-1}dp[i][k]*g[cnt[i][j]]
dp[i][j]=g[i]−∑k=i+1j−1dp[i][k]∗g[cnt[i][j]]。dp方程的含义是枚举
i
i
i没有与
j
j
j连通,那么与它连通的最大数是
j
j
j,后面的那些点随便连,减去这些情况的方案数。这样当前问题就可以由之前的子问题计算得来了。
最后统计一下答案即可。
代码:
#include <bits/stdc++.h>
using namespace std;
const long long mod=1e9+7;
int n,k,a[610],b[610],pd[610];
long long cnt[610][610],dp[610][610],g[2000],ans;
int main()
{
scanf("%d%d",&n,&k);
for(int i=1;i<=k;++i)
{
scanf("%d%d",&a[i],&b[i]);
pd[a[i]]=b[i];
pd[b[i]]=a[i];
}
n<<=1;
g[0]=1;
for(int i=2;i<=n;i+=2)
g[i]=(g[i-2]*(i-1))%mod;
for(int i=1;i<=n;++i)
{
for(int j=i;j<=n;++j)
cnt[i][j]=cnt[i][j-1]+(!pd[j]);
}
for(int i=1;i<=n;++i)
{
for(int j=i;j<=n;++j)
{
int ji=0;
for(int k=i;k<=j;++k)
{
if(pd[k]&&(pd[k]>j||pd[k]<i))
ji=1;
}
if(ji==1)
continue;
dp[i][j]=g[cnt[i][j]];
for(int k=i+1;k<=j-1;++k)
dp[i][j]=(dp[i][j]-1ll*dp[i][k]*g[cnt[k+1][j]]%mod+mod)%mod;
}
}
for(int i=1;i<=n;++i)
{
for(int j=i;j<=n;++j)
ans=(ans+dp[i][j]*g[(n/2-k)*2-cnt[i][j]]%mod)%mod;
}
printf("%lld\n",ans);
return 0;
}