[PA2018]Skwarki
一眼 dp,发现列不出状态。排列计数问题遇到这种情况,考虑笛卡尔树。
发现一次删除对应到笛卡尔树上就是删掉所有儿子数不为 的节点。特别地,对于当前树中下标最小/最大的点,它们没有儿子时才会被删除。
然后这不随便乱 dp?其实这个 dp 属实有点恶心,我比赛的时候搞了1h dp还是错的,所以这里会写得比较详细。
我们记一个点 的 表示这个点在 时刻被删除。记一颗子树的 表示这颗子树中的所有点在 时刻被删除。
根据上面的分析,对于 号点和 号点需要特殊处理。所以设状态为 表示有多少种有 个节点, 值为 的子树,子树内包含 0/1/2 个 号或 号节点。
转移时肯定要枚举两个子树分别有多少个点。记 。假设其中一个子树(不妨设为左子树)有 个点(由于需要给左右子树分配权值,以下所有方程均需要带上 的系数):
对于 ,考虑这个 值的来源:
-
左子树的 。。
-
右子树的 。。
-
当前这个节点的 。除开上面两种情况,还有 种。
对于 :
不妨假设这颗子树当前包含 号点,且 号点在它的左子树中。
注意到如果左子树删完了,只要右子树还在,那么当前这个点就属于下标最小且有一个儿子的点,不会被删除。因此,这个节点的 完全由它的右子树决定,且它的 就是右子树的 加 。
考虑这个 值的来源:
-
左子树的 。。(这里减 是为了避免和下面 来自当前节点的情况重复)。
-
右子树的 。根据上面分析,它一定比当前节点的 小,所以不可能。
-
当前这个节点的 。。
对于 :
注意这种状态表明当前这颗笛卡尔树已经对应一个排列了,所以不可能再扩充了。
显然,左右子树都必须恰有一个 或 号点,且左右子树只要有一个还没被删完,就删不掉这个点。所以这个点的 是两边子树 的 加 ,并且 一定来源于当前这个点。
-
在左子树。。
-
在右子树。
当然,以上所有方程都没有考虑只有左/右子树的情况。这种情况是简单的,看懂了状态就能写出来。
#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 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步