Atcoder Grand Contest 028
028D Chords
题目描述
解法
首先考虑把问题转化到序列上,可以看成序列上的两点匹配,如果匹配形成的区间相交则看成有边。
一个关键的 \(\tt observation\) 是:任意联通块一定可以被某个区间完全包含,并且这个区间的两个端点都在连通块内。这说明我们可以通过枚举区间的形式来枚举连通块,设 \(f(i,j)\) 表示区间 \([i,j]\) 任意连边(但是不能连出去),最后 \(i,j\) 在同一连通块内的方案数,那么可以直接枚举连通块来算贡献:
\[ans=\sum f(i,j)\cdot g(2n-2m-c(i,j))
\]
其中 \(g(x)=1\cdot 3\cdot 5\ ...\ (x-1)\) 表示 \(x\) 个点的序列任意连边的方案数,\(c(i,j)\) 表示区间 \([i,j]\) 之间未匹配点的个数。
现在考虑 \(f(i,j)\) 的转移,正难则反,首先让 \(f(i,j)=g(c(i,j))\),然后减去 \(i,j\) 不在同一连通块的方案,这里我们可以枚举 \(i\) 真实连通块的右端点是 \(k\),那么就找到了子问题:
\[f(i,j)=g(c(i,j))-\sum_{k=i}^{j-1}f(i,k)\cdot g(c(k+1,j))
\]
时间复杂度 \(O(n^3)\)
#include <cstdio>
const int M = 605;
const int MOD = 1e9+7;
#define int long long
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,m,ans,mt[M],f[M][M],g[M];
signed main()
{
n=read()<<1;m=read();
for(int i=1;i<=m;i++)
{
int x=read(),y=read();
mt[x]=y;mt[y]=x;
}
g[0]=1;
for(int i=2;i<=n;i+=2)
g[i]=g[i-2]*(i-1)%MOD;
for(int i=n;i>=1;i--) for(int j=i+1;j<=n;j+=2)
{
int ok=1,c=j-i+1;
for(int k=i;k<=j;k++)
if(mt[k] && (--c,mt[k]<i || mt[k]>j)) ok=0;
if(!ok) continue;
f[i][j]=g[c];
for(int k=j,d=0;k>i;k--)
d+=!mt[k],f[i][j]=(f[i][j]-f[i][k-1]*g[d])%MOD;
ans=(ans+f[i][j]*g[n-2*m-c])%MOD;
}
printf("%lld\n",(ans+MOD)%MOD);
}