gmoj 7066. 【2021.4.24 NOI模拟】ehzeux与圆周(circle)

首先我们考虑转换思维,考虑每一个联通块对答案的贡献。

(f[i][j]) 表示当前联通块中最小编号为 (i),最大编号为 (j) 的方案数,(i)(j) 里面的点是要全连过边的并且没有连出去块的边,而且(i)(j) 相连,那么它对答案的贡献就是 (除去 ([i,j]) 和已配对的点还剩 (x) 个点,剩下的点随便配对的方案数,记为 (g[x])) 。

我们考虑 (f[i][j]) 怎么求 。考虑用总数减去不合法的,将 ([i,j]) 这些点在块内随便连,再减去 (i)(j) 不配对的方案数就是 (f[i][j]) 。记 ([i,j]) 里面有 (h(i,j)) 个没有被配对的点,那么我们有

[f[i][j]=g[h(i,j)]-sum_{l=i+1}^{j-1}f[i][l]*g[h(l+1,j)] ]

意思就是,如果 (i)(l) 连边了,那么 ([i,j]) 这段联通块的方案数为 (f[i][l]*g[h(l+1,j)]) ,减掉所有 (l) 的可能情况就行了。 顺便说一下如果 ([i,j]) 之间有点配对到了这个联通块外面,那么 (f[i][j]=0)

附上代码

#include<cstdio>
using namespace std;

const int N=605,mo=1e9+7;
int a[N],f[N][N],g[N],s[N];
int n,m,mi,ma,an,i,j,k,x,y;

int main(){
	freopen("circle.in","r",stdin);
	freopen("circle.out","w",stdout);
	scanf("%d%d",&n,&m); n*=2;
	for(i=1;i<=m;++i)
		scanf("%d%d",&x,&y),a[x]=y,a[y]=x,s[x]=s[y]=1;
	for(i=1;i<=n;++i){
		if (!a[i]) a[i]=i;
		s[i]+=s[i-1];
	}
	g[0]=1;
	for(i=2;i<=n;i+=2)
		g[i]=1ll*g[i-2]*(i-1)%mo;
	for(i=1;i<=n;++i){
		mi=ma=a[i];
		for(j=i+1;j<=n;++j){
			if (a[j]<mi) mi=a[j];
			if (a[j]>ma) ma=a[j];
			if (mi<i||ma>j) continue;
			x=j-i+1-s[j]+s[i-1];
			f[i][j]=g[x];
			if (!g[x]) continue;
			for(k=i+1;k<j;++k)
				f[i][j]=(-1ll*f[i][k]*g[j-k-s[j]+s[k]]%mo+f[i][j]+mo)%mo;
			an=(an+1ll*f[i][j]*g[n-s[n]-x])%mo;
		}
	}
	printf("%d",an);
	return 0;
}
原文地址:https://www.cnblogs.com/zsjz-yzy/p/14719497.html