把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【AT4438】[AGC028D] Chords(动态规划)

点此看题面

  • 有一个\(2n\)个点构成的环,每个点恰好连出一条边。
  • 给定\(k\)条边,剩下的点之间随意连边,求所有连边方案下连通块个数的总和。
  • \(n\le300\)

枚举连通块算贡献

发现这种连通块的贡献很难一起计算,一般的套路应该是分别考虑每一个连通块。

但直接枚举所有连通块又显得不太现实。。。

实际上,我们考虑一类连通块,它们最左端和最右端的点分别是\(l,r\),显然多个同类连通块不可能并存,因此可以想到去计算这类连通块的贡献。

但一个连通块并不一定完整包含整个区间,中间可能存在几个小连通块,不过没太大关系,因为我们原本就要考虑所有方案下连通块的贡献,中间的连边方案本来也就是要考虑到最终方案中的。

容斥+动态规划

按照之前说的,我们枚举\(i,j\),令\(f_{i,j}\)表示左右端点分别为\(i,j\)的连通块个数。

首先\(i,j\)连通块可能存在,需要满足\(j-i+1\)是偶数(因为要两两连边),且不存在一条给定边的一端在\([i,j]\)内,一端在\([i,j]\)外。

枚举边的过程中顺带统计出连通块内尚未确定边的点数\(c_{i,j}\)

\(g_x\)表示\(x\)个点之间两两连边的方案数,只要考虑第一个点和谁连边就能转化成\(x-2\)个点之间连边的递归问题,得到\(g_x=g_{x-2}\times(x-1)\)

那么,粗略计算\(f_{i,j}\)就能得到\(f_{i,j}=g_{c_{i,j}}\),但我们无法保证\(i,j\)连通,于是就要请出连通块问题的经典容斥,枚举\(i\)所在的连通块:

\[f_{i,j}=g_{c_{i,j}}-\sum_{p=i}^{j-1}f_{i,p}\times g_{c_{p+1},j} \]

最后统计答案就是枚举每个连通块,其余的点任意连边,得到:

\[ans=\sum f_{i,j}\times g_{n-2k-c_{i,j}} \]

代码:\(O(n^3)\)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 300
#define X 1000000007
using namespace std;
int n,k,x[N+5],y[N+5],c[2*N+5][2*N+5],s[2*N+5],f[2*N+5][2*N+5],g[2*N+5];
int main()
{
	RI i,j;for(scanf("%d%d",&n,&k),i=1;i<=k;++i) scanf("%d%d",x+i,y+i);
	for(n<<=1,g[0]=1,i=2;i<=n;++i) g[i]=1LL*g[i-2]*(i-1)%X;//预处理g[i]
	RI l,p,t=0;for(l=2;l<=n;l+=2) for(i=1,j=l;j<=n;++i,++j)//区间DP
	{
		#define In(x) (i<=x&&x<=j)//判断x是否在区间内
		for(c[i][j]=j-i+1,p=1;p<=k;++p) if((c[i][j]-=In(x[p])+In(y[p]))&1) break;if(p<=k) continue;//枚举每条边判合法,同时计算区间内尚未连边点数
		for(f[i][j]=g[c[i][j]],p=i+1;p^j;p+=2) f[i][j]=(f[i][j]-1LL*f[i][p]*g[c[p+1][j]]%X+X)%X;//枚举i所在连通块容斥
		t=(1LL*f[i][j]*g[n-2*k-c[i][j]]+t)%X;//统计每个连通块对答案的贡献
	}return printf("%d\n",t),0;
}
posted @ 2021-04-05 20:12  TheLostWeak  阅读(122)  评论(0编辑  收藏  举报