Loading

[dp 记录]agc024E Sequence Growing Hard

题意:

给定一个序列,每次往其中插入 \([1,k]\) 间的一数,要求字典序递增,求方案数。设插入 \(i\) 数的数列为 \(A_i\),存在一个 \(A_i\) 不同即两插入方案不同,否则即一致。

\(n,k \leq 300\)

添加一个数,该数后面第一个与它数字的数字小于它本身。注意到连续相同数可以插入到任意位置,不妨钦定插到最后面,则每次转移时 \(x_i > x_{i+1}\)

这样仍然不好做。无论枚举填进去的数还是枚举填的位置都需要知道 \(n\) 个数的详细信息。

注意到操作序列能唯一确认方案,我们渴望找到什么能与操作序列双射。

看到操作序列,尤其转移与大小相关,可以考虑上树。

考虑怎样计数操作,不会做。试想更简单的问题:对于一个确定的序列,怎样刻画“插入”操作并对其计数?根据性质,一个数会插入在另一个数前。因此,可以考虑向位置靠后的数连边。这样,每个位置都有一个出边,只有虚点没有出边。所以这是一棵树,树上每个节点的父亲的权值都严格小于它。

—— https://www.luogu.com.cn/blog/Yansuan-HCl/solution-at-agc024-e

这样还是不易于统计,再做一步转化,把每个字符的位置用它被插入的时间表示。那么我们需要做的即是对每次操作,选择之前的一次操作表示这次插在之前插的位置前面。特别地,选择 \(0\) 操作表示这次插在最后面。

——https://www.luogu.com.cn/blog/0123456-3456789/solution-at3962

一次插入操作可以用它插入时在它后面的数刻画。所以连边,自然构成一棵树。

定义一个节点的权值为二元组 \((val, id)\),每次操作新建点挂到它后面的点下面,\(id\) 为插入时间,\(val\) 为权值。则 \(\forall v\)\(u\) 儿子,\(val_v > val_u,id_v > id_u\)。容易验证这样的树与操作序列双射。这是漂亮的有传递性的偏序关系,无后效性的子结构,可以进行 dp 了。

记录三维(大小也是应该关心的)还是多了些。发现每次转移相当于加一棵子树进来,钦定那是 \(id\) 最小或 \(val\) 最小就可以不用记录其中一者转移了。

\(id\) 是排列,关于排列的平移是唯一的,所以钦定 \(id\) 最小是好做的。

\(dp_{v,x}\) 表示根节点 \(val=v,siz=x\) 的树的方案数。转移:

\[dp_{v,x} = \sum_{i=v+1}^{k} \sum_{y=1}^{x-1} dp_{i,y}dp_{v,x-y}\binom{x-2}{y-1} \]

后面的组合数是因为两边的 \(val\) 的相对顺序没有限制,除了两个根以外的相对顺序都可以随意选择。

\(i\) 这一维后缀和滚掉即可做到 \(O(n^3)\)

#include <cstdio>
using namespace std;
const int M = 305;
int mod;
int add(int a, int b) {a += b; return a > mod ? a-mod : a;}
int mins(int a, int b) {a -= b; return a < 0 ? a+mod : a;}
void addn(int &x, int y) {x += y; if(x > mod) x -= mod;}
int n, dp[M][M], k, C[M][M], s[M][M];
int main(){
    scanf("%d %d %d", &n, &k, &mod);
    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] = add(C[i-1][j-1], C[i-1][j]);
        printf("\n");
    }
    for(int i = 0; i <= k; i++) dp[i][1] = 1, s[i][1] = k-i+1;
    for(int x = 2; x <= n+1; x++) {
        for(int v = k; v >= 0; v--) {
            for(int y = 1; y < x; y++) 
                addn(dp[v][x], 1ll * C[x-2][y-1] * s[v+1][y] % mod * dp[v][x-y] % mod);
            s[v][x] = add(s[v+1][x], dp[v][x]);
        }
    }
    printf("%d\n", dp[0][n+1]);
}
posted @ 2022-12-11 15:37  purplevine  阅读(22)  评论(0编辑  收藏  举报