[ABC217]F - Make Pair

[ABC217]F - Make Pair

题目

\(2N\)个学生,编号\(1,2,3\ldots 2N\),其中有\(M\)对学生关系友好,教师每次可以从中选出一对相邻的学生,且他们关系友好,然后将这对学生删除(注意删除后学生的相邻关系改变).

求出删除所有学生的方案数,取模.

思路

这种问方案数,还取模的题基本都是DP.看下数据范围大概在\(O(n^3)\)左右.

应该不难想到区间DP.

\(f_{i,j}\)表示消掉\(i\sim j\)所有学生的方案数.考虑如何从小区间推到大区间,我们把第\(i\)个人单独拿出来,枚举\(k\in [i+1, j]\),如果第\(i\)个人和第\(k\)个人关系友好,我们可以先合并\(i+1\sim k-1\)的人,然后合并\(i,k\)\(k+1\sim j\)(后面这两个顺序是可以换的).为了方便,令\(n_1=k-i+1\),\(n_2=j-k\)(两边的人数),产生的贡献就是

\[f_{i,k}\cdot f_{k+1,j}\cdot C^{n_2}_{n_1+n_2} \]

这是一个经典的组合数问题.

我们称\(i\sim k\)为左半边,\(k+1\sim j\)为右半边.

设合并左半边的操作序列为\(A_1,A_2,\ldots ,A_{n_1}\),合并右半边的操作序列为\(B_1,B_2,\ldots ,B_{n_2}\),显然,合并\(i\sim j\)\(A,B\)原来的操作顺序不能改变,即对于所有的\(i\in [2,n_1]\),\(A_{i-1}\)要在\(A_i\)之前出现,对于\(B\)也同理.

原问题等价于从\(n_1+n_2\)个小球中选出\(n_1\)个球的方案数,这样理解:所有小球等价于合并全区间的操作序列, 选出的小球代表左半边的操作序列,剩下的代表右半边的操作序列,比如,6个小球中,选出第\(1,2,4,6\)个,对应的合并后的操作序列就是\(A_1,A_2,B_1,A_3,B_2,A_4\).

因此,合并\(i\sim j\)所有人的操作方案数就是\(C^{n_2}_{n_1+n_2}\).

最后,答案显然就是\(f_{1,2n}\).

代码

#include <iostream>
#include <cstdio>
using namespace std;

typedef long long lint;
const int N = 410;
const lint mod = 998244353;
int n , m;
int rel[N][N];
lint f[N][N];

lint fac[N];
lint inv[N];

lint pow_(lint a , int p) {
	lint res = 1;
	while(p) {
		if(p & 1)
			res = res * a % mod;
		a = a * a % mod;
		p >>= 1;
	}
	return res;
}
signed main() {
	cin >> n >> m;
	n *= 2;
	for(int i = 1 ; i <= m ; i++) {
		int u , v;
		cin >> u >> v;
		rel[u][v] = rel[v][u] = 1;
		if(u == v + 1 || v == u + 1)
			f[u][v] = f[v][u] = 1;
	}

	fac[0] = 1;
	for(int i = 1 ; i <= n ; i++)
		fac[i] = fac[i - 1] * i % mod;
	for(int i = 1 ; i <= n ; i++)
		inv[i] = pow_(fac[i] , mod - 2);

	for(int i = n ; i > 0 ; i--)
		for(int j = i + 3 ; j <= n ; j+=2) {
			if(j - i + 1 & 1)
				continue;
			if(rel[i][j])
				f[i][j] += f[i + 1][j - 1];
			for(int k = i + 1 ; k < j ; k += 2)
				if(rel[i][k])
					f[i][j] += f[i + 1][k - 1] * f[k + 1][j] % mod
					           * fac[j - i + 1 >> 1] % mod
					           * inv[j - k >> 1] % mod
					           * inv[j - i + 1 - (j - k) >> 1] % mod ,
					f[i][j] %= mod;
		}
	cout << f[1][n];
	return 0;
}
posted @ 2021-09-08 19:07  追梦人1024  阅读(158)  评论(0编辑  收藏  举报
Live2D