[PA2018]Skwarki

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

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

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

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

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

转移时肯定要枚举两个子树分别有多少个点。记 sumi,j,0/1/2=k=0jdpi,k,0/1/2。假设其中一个子树(不妨设为左子树)有 k 个点(由于需要给左右子树分配权值,以下所有方程均需要带上 (i1k) 的系数):

对于 dpi,j,0,考虑这个 T 值的来源:

  1. 左子树的 Tdpk,j,0×sumik1,j1,0

  2. 右子树的 Tsumk,j1,0×dpik1,j,0

  3. 当前这个节点的 T。除开上面两种情况,还有 dpk,j1,0×dpik1,j1,0 种。

对于 dpi,j,1

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

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

考虑这个 T 值的来源:

  1. 左子树的 Tdpk,j,1×sumik1,j2,0。(这里减 2 是为了避免和下面 T 来自当前节点的情况重复)。

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

  3. 当前这个节点的 Tsumk,j1,1×dpik1,j,0

对于 dpi,j,2

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

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

  1. max 在左子树。dpk,j1,0×sumik1,j1,0

  2. max 在右子树。sumk,j2,0×dpik1,j1,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;
}

本文作者:zqs2020

本文链接:https://www.cnblogs.com/stinger/p/16646992.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   zqs2020  阅读(145)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起
  1. 1 404 not found REOL
404 not found - REOL
00:00 / 00:00
An audio error has occurred.