版权声明:本文为博主原创文章,可以转载但是必须声明版权。 https://blog.csdn.net/forever_shi/article/details/83109712
题目链接
题意:
在一个环上有2n个点,按顺时针编号,你需要将这些点两两配对相连,形成若干个连通块。连通的含义是只要连接的两个点能通过它配对点的直线,经过与其他线段的交点能到达另一条线。现在已经有k对点配对好了,告诉你这k对点的编号,问你剩下的n-k对的所有配对方法最后形成的连通块数量之和。对1e9+7取模。
题解:
我还是水平好菜的,遇到一些有难度的计数题还是毫无思路啊。
这题考虑dp,我们设dp[i][j]为i与j连通,并且连通块内最小编号是i,最大编号是j的方案数,那么它对答案的贡献就是
其他没连的随便连的方案数。我们先
统计出
到
的中间有多少个点没有连过,记为
,然后考虑如何计算dp数组。我们发现直接算并不好算,我们可以用
到
中的数随便连的方案数减去其中没有让
和
连通的方案数。我们预处理出
个点随便连的方案数
,对于
是奇数,答案是0,对于偶数
,答案是
。那么对于
到
之间没有连到
和
以外的边的情况(连出去最小数和最大数就不是
和
了),我们有
。dp方程的含义是枚举
没有与
连通,那么与它连通的最大数是
,后面的那些点随便连,减去这些情况的方案数。这样当前问题就可以由之前的子问题计算得来了。
最后统计一下答案即可。
代码:
#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;
}