ARC138E Decreasing Subsequence

本文最后更新于:April 11, 2022 am

第一步是巧妙的转化:对于 $a_i>0$,连边 $(i,a_i-1)$。

对于每个序列 $a$,连边后形成若干条链

假设选出的 $k$ 个点为 $b_1,b_2,…,b_k$,并且 $b_1<b_2<…<b_k$,对应的 $a_{b_1}-1,…,a_{b_k}-1$ 为了方便记作 $c_1,c_2,…,c_k$,则有:

$$c_k<c_{k-1}<…<c_1<b_1<b_2<…<b_k$$

这可以由 $b_1>c_1$,$c$ 递减,$b$ 递增推导出来。

也就是说,在 $a$ 确定的前提下,在 $[0,n]$ 中选出 $2k$ 个点,它们对应的子序列有且仅有一种

考虑枚举前 $k$ 个点以及所在链上的其他点的个数 $i$,后 $k$ 个点以及所在链上的其他点的个数 $j$,则方案数为:

$$\dbinom{n+1}{i+j}\begin{Bmatrix}i\\k\end{Bmatrix}\begin{Bmatrix}j\\k\end{Bmatrix}\sum\limits_{x=1}^{n+1-i-j}\begin{Bmatrix}n+1-i-j\\x\end{Bmatrix}$$

将 $i$ 个点分成 $j$ 条链的方案数等价于第二类斯特林数,因为链上总是由编号大的指向编号小的。

注意一下实现做个前缀和就可以做到 $\mathrm{O(n^2)}$。

$\texttt{Code:}$

#include <bits/stdc++.h>
using namespace std;
const int cmd = 1e9 + 7;
inline int add(int a, int b) {a += b; return a < cmd ? a : a - cmd;}
inline int sub(int a, int b) {a -= b; return a < 0 ? a + cmd : a;}
inline int mul(int a, int b) {return 1ll * a * b % cmd;}
int fpow(int a, int b) {
    int res = 1;
    for (; b; b >>= 1, a = mul(a, a))
        if (b & 1) res = mul(res, a);
    return res;
}
const int maxn = 5e3 + 5;
int n, k, S2[maxn][maxn], fac[maxn], ifac[maxn];
int C(int n, int m) {return mul(fac[n], mul(ifac[m], ifac[n - m]));}
int main() {
    scanf("%d%d", &n, &k);
    S2[1][1] = 1;
    for (int i = 2; i <= n + 1; i++)
    for (int j = 1; j <= i; j++)
        S2[i][j] = add(S2[i - 1][j - 1], mul(S2[i - 1][j], j));
    fac[0] = ifac[0] = 1;
    for (int i = 1; i <= n + 1; i++) {
        fac[i] = mul(fac[i - 1], i);
        ifac[i] = fpow(fac[i], cmd - 2);
    }
    int ans = 0;
    for (int i = k + k; i <= n + 1; i++) {
        int s1 = 0, s2 = 0;
        for (int j = k; j <= i - k; j++)
            s1 = add(s1, mul(S2[j][k], S2[i - j][k]));
        if (i < n + 1) {
            for (int j = 1; j <= n + 1 - i; j++)
                s2 = add(s2, S2[n + 1 - i][j]);
        } else s2 = 1;
        ans = add(ans, mul(s1, mul(s2, C(n + 1, i))));
    }
    printf("%d", ans);
    return 0;
}

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!