一种奇妙的块状DP

这种 \(DP\) 可以用来求一种合法序列的方案数

介绍

\(f_{i,j}\) 为前 \(i\) 个数分成 \(j\) 个合法序列的方案数

为了转移方便, 避免讨论, 我们一般会进行排序, 让他拥有一些性质

转移一般有三种

  • 合并两个序列 \(f[i][j] = f[i - 1][j + 1] * j + f[i][j]\)

  • 新开一个序列 \(f[i][j] = f[i][j] + f[i - 1][j - 1] * j\)

  • 在一个序列中新加入元素 \(f[i][j] = f[i][j] + f[i - 1][j] * j\)

例题

一个合法序列必须得是波浪形状的, 且开头和结尾必须是 \(s\)\(t\)

我们先从小到大排序, 然后可以发现当前的数比前边的数都大, 于是可以合并, 形成一个新的合法序列

也可以新开一个序列

但是对于加入元素就不可以了

对于 \(s\)\(t\) 的限制, 只需要关注新开序列开在开头和结尾的限制就可以了

    f[1][1] = 1;
    for (ll i = 2; i <= n; i++) 
        for (ll j = 1; j <= i; j++){
            if (i == s || i == t) f[i][j] = (f[i - 1][j - 1] + f[i - 1][j]) % mod;
            else f[i][j] = (f[i - 1][j + 1] * j % mod + f[i - 1][j - 1] * (j - (i > s ? 1 : 0) - (i > t ? 1 : 0))) % mod;
        }
    printf("%lld", f[n][1]);

大意:

现在有一张 \(2n\) 个点的二分图,左边 \(n\) 个点,右边 \(n\) 个点。其中左边第 \(i\) 个点与右边 \(1 - a_i\) 个点有边。

你需要求出图中总共有多少个不同的简单环。简单环在这里指一个点只被经过至多一次的环。

答案对 \({10}^9 + 7\) 取模。

首先破环成链

然后我们观察到一个合法的链类似于上方的波浪性, 然后再通过一个点合并成环

所以我们先排序, 发现随着枚举左边的点一定包含之前所有的右部点

对于右部点可以新增一个序列

对于左部点可以合并两个序列, 同时把一个链合并成一个环

不一样的地方是这次合并不是相邻的两个序列, 而是任意两个, 所以需要 \(j * (j - 1)\)

因为一条链首尾不同我们视为是不同的, 所以一个环被算了两次, 最后除于 2 即可

两个点不算作一个环, 所以要减去

    scanf("%lld", &n);
    for (ll i = 1; i <= n; i++) scanf("%lld", a + i), ans = (ans - a[i] + mod) % mod;
    std::sort(a + 1, a + n + 1);
    f[0][0] = 1;
    for (ll i = 1; i <= n; i++) {
        for (ll j = a[i - 1] + 1; j <= a[i]; j++)
            for (ll k = j; k >= 1; k--) f[i - 1][k] = (f[i - 1][k - 1] + f[i - 1][k]) % mod;
        ans = (ans + f[i - 1][1]) % mod;
        for (ll j = 0; j <= a[i]; j++)
            f[i][j] = (f[i - 1][j + 1] * j % mod * (j + 1) % mod + f[i - 1][j]) % mod;
    }
    printf("%lld", ans * quickly_pow(2, mod - 2) % mod);
posted @ 2024-02-20 14:38  d3genera7e  阅读(5)  评论(0编辑  收藏  举报