[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\) 值的来源:
-
左子树的 \(T\)。\(dp_{k,j,0}\times sum_{i-k-1,j-1,0}\)。
-
右子树的 \(T\)。\(sum_{k,j-1,0}\times dp_{i-k-1,j,0}\)。
-
当前这个节点的 \(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\) 值的来源:
-
左子树的 \(T\)。\(dp_{k,j,1}\times sum_{i-k-1,j-2,0}\)。(这里减 \(2\) 是为了避免和下面 \(T\) 来自当前节点的情况重复)。
-
右子树的 \(T\)。根据上面分析,它一定比当前节点的 \(T\) 小,所以不可能。
-
当前这个节点的 \(T\)。\(sum_{k,j-1,1}\times dp_{i-k-1,j,0}\)。
对于 \(dp_{i,j,2}\):
注意这种状态表明当前这颗笛卡尔树已经对应一个排列了,所以不可能再扩充了。
显然,左右子树都必须恰有一个 \(1\) 或 \(n\) 号点,且左右子树只要有一个还没被删完,就删不掉这个点。所以这个点的 \(T\) 是两边子树 \(T\) 的 \(\max\) 加 \(1\),并且 \(T\) 一定来源于当前这个点。
-
\(\max\) 在左子树。\(dp_{k,j-1,0}\times sum_{i-k-1,j-1,0}\)。
-
\(\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;
}