题目
有(2N)个学生,编号(1,2,3ldots 2N),其中有(M)对学生关系友好,教师每次可以从中选出一对相邻的学生,且他们关系友好,然后将这对学生删除(注意删除后学生的相邻关系改变).
求出删除所有学生的方案数,取模.
思路
这种问方案数,还取模的题基本都是DP.看下数据范围大概在(O(n^3))左右.
应该不难想到区间DP.
设(f_{i,j})表示消掉(isim j)所有学生的方案数.考虑如何从小区间推到大区间,我们把第(i)个人单独拿出来,枚举(kin [i+1, j]),如果第(i)个人和第(k)个人关系友好,我们可以先合并(i+1sim k-1)的人,然后合并(i,k)和(k+1sim j)(后面这两个顺序是可以换的).为了方便,令(n_1=k-i+1),(n_2=j-k)(两边的人数),产生的贡献就是
这是一个经典的组合数问题.
我们称(isim k)为左半边,(k+1sim j)为右半边.
设合并左半边的操作序列为(A_1,A_2,ldots ,A_{n_1}),合并右半边的操作序列为(B_1,B_2,ldots ,B_{n_2}),显然,合并(isim j)时(A,B)原来的操作顺序不能改变,即对于所有的(iin [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).
因此,合并(isim 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;
}