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);
}
posted @ 2022-03-23 08:45  C202044zxy  阅读(87)  评论(0编辑  收藏  举报