[PA2018]Skwarki

一眼 dp,发现列不出状态。排列计数问题遇到这种情况,考虑笛卡尔树。

发现一次删除对应到笛卡尔树上就是删掉所有儿子数不为 \(2\) 的节点。特别地,对于当前树中下标最小/最大的点,它们没有儿子时才会被删除。

然后这不随便乱 dp?其实这个 dp 属实有点恶心,我比赛的时候搞了1h dp还是错的,所以这里会写得比较详细。

我们记一个点 \(u\)\(T(u)\) 表示这个点在 \(T(u)\) 时刻被删除。记一颗子树的 \(T\) 表示这颗子树中的所有点在 \(T\) 时刻被删除。

根据上面的分析,对于 \(1\) 号点和 \(n\) 号点需要特殊处理。所以设状态为 \(dp_{i,j,0/1/2}\) 表示有多少种有 \(i\) 个节点,\(T\) 值为 \(j\) 的子树,子树内包含 0/1/2 个 \(1\) 号或 \(n\) 号节点。

转移时肯定要枚举两个子树分别有多少个点。记 \(sum_{i,j,0/1/2}=\sum\limits^j_{k=0}dp_{i,k,0/1/2}\)。假设其中一个子树(不妨设为左子树)有 \(k\) 个点(由于需要给左右子树分配权值,以下所有方程均需要带上 \(\tbinom{i-1}{k}\) 的系数):

对于 \(dp_{i,j,0}\),考虑这个 \(T\) 值的来源:

  1. 左子树的 \(T\)\(dp_{k,j,0}\times sum_{i-k-1,j-1,0}\)

  2. 右子树的 \(T\)\(sum_{k,j-1,0}\times dp_{i-k-1,j,0}\)

  3. 当前这个节点的 \(T\)。除开上面两种情况,还有 \(dp_{k,j-1,0}\times dp_{i-k-1,j-1,0}\) 种。

对于 \(dp_{i,j,1}\)

不妨假设这颗子树当前包含 \(1\) 号点,且 \(1\) 号点在它的左子树中。

注意到如果左子树删完了,只要右子树还在,那么当前这个点就属于下标最小且有一个儿子的点,不会被删除。因此,这个节点的 \(T\) 完全由它的右子树决定,且它的 \(T\) 就是右子树的 \(T\)\(1\)

考虑这个 \(T\) 值的来源:

  1. 左子树的 \(T\)\(dp_{k,j,1}\times sum_{i-k-1,j-2,0}\)。(这里减 \(2\) 是为了避免和下面 \(T\) 来自当前节点的情况重复)。

  2. 右子树的 \(T\)。根据上面分析,它一定比当前节点的 \(T\) 小,所以不可能。

  3. 当前这个节点的 \(T\)\(sum_{k,j-1,1}\times dp_{i-k-1,j,0}\)

对于 \(dp_{i,j,2}\)

注意这种状态表明当前这颗笛卡尔树已经对应一个排列了,所以不可能再扩充了。

显然,左右子树都必须恰有一个 \(1\)\(n\) 号点,且左右子树只要有一个还没被删完,就删不掉这个点。所以这个点的 \(T\) 是两边子树 \(T\)\(\max\)\(1\),并且 \(T\) 一定来源于当前这个点。

  1. \(\max\) 在左子树。\(dp_{k,j-1,0}\times sum_{i-k-1,j-1,0}\)

  2. \(\max\) 在右子树。\(sum_{k,j-2,0}\times dp_{i-k-1,j-1,0}\)

当然,以上所有方程都没有考虑只有左/右子树的情况。这种情况是简单的,看懂了状态就能写出来。

#include <cstdio>

int dp[1005][12][3], sum[1005][12][3], C[1005][1005], mod;
inline void add(int &x, const int y) {
	if ((x += y) >= mod) x -= mod;
}

int main() {
	int n, m;
	scanf("%d%d%d", &n, &m, &mod);
	if (m >= 12) return puts("0"), 0;			
	if (n == 1) return puts(m ? "0" : "1"), 0;
	C[0][0] = 1;
	for (int i = 1; i <= n; ++ i) {
		C[i][0] = 1;
		for (int j = 1; j <= i; ++ j) C[i][j] = (C[i - 1][j] + C[i - 1][j - 1]) % mod;
	}
	dp[1][0][0] = dp[1][0][1] = 1;
	for (int i = 0; i <= 11; ++ i) sum[1][i][0] = sum[1][i][1] = 1;
	for (int i = 2; i <= n; ++ i)
	for (int j = 0; j <= m; ++ j) {
		dp[i][j][0] = dp[i - 1][j][0], add(dp[i][j][0], dp[i - 1][j][0]);
		dp[i][j][1] = dp[i - 1][j][1];
		if (j) {
			add(dp[i][j][1], dp[i - 1][j - 1][0]);
			dp[i][j][2] = dp[i - 1][j - 1][1], add(dp[i][j][2], dp[i - 1][j - 1][1]);
		}
		if (j) for (int k = 1; k < i - 1; ++ k) {
			dp[i][j][0] = ((1ll * dp[k][j - 1][0] * dp[i - k - 1][j - 1][0]
			+ 1ll * dp[k][j][0] * sum[i - k - 1][j - 1][0] + 1ll * sum[k][j - 1][0] * dp[i - k - 1][j][0])
			% mod * C[i - 1][k] + dp[i][j][0]) % mod;
			dp[i][j][1] = ((1ll * dp[k][j - 1][0] * sum[i - k - 1][j][1]
			+ (j > 1 ? 1ll * sum[k][j - 2][0] * dp[i - k - 1][j][1] : 0ll)) % mod * C[i - 1][k] + dp[i][j][1]) % mod;
			dp[i][j][2] = ((1ll * dp[k][j - 1][1] * sum[i - k - 1][j - 1][1]
			+ (j > 1 ? 1ll * sum[k][j - 2][1] * dp[i - k - 1][j - 1][1] : 0ll)) % mod * C[i - 1][k] + dp[i][j][2]) % mod;
		}
		sum[i][j][0] = dp[i][j][0], sum[i][j][1] = dp[i][j][1], sum[i][j][2] = dp[i][j][2];
		if (j) {
			add(sum[i][j][0], sum[i][j - 1][0]);
			add(sum[i][j][1], sum[i][j - 1][1]);
			add(sum[i][j][2], sum[i][j - 1][2]);
		}
	}
	printf("%d", dp[n][m][2]);
	return 0;
}
posted @ 2022-09-01 16:31  zqs2020  阅读(139)  评论(0编辑  收藏  举报