Reordering(组合计数)

题意

给定一个字符串\(S\)。通过将\(S\)的非空、不一定连续的子序列置换,可以得到多少个不同的字符串?
\(S\)只包含小写字母。

数据范围

\(1 \leq |S| \leq 5000\)

思路

其实很容易发现,这个问题等价于:给定\(a \sim z\)每个字母的数量,问这些字母可以组成多少个不同的字符串。

纯用计数的方法非常困难,于是考虑使用DP。令\(f(i, j)\)表示使用前\(i\)个字母,能组成长度为\(j\)的字符串的数量。
则,\(f(i, j) = \sum\limits_{k = 0}^{\min(j, freq_i)}f(i - 1, j - k)\tbinom{j}{k}\)
其中,\(freq_i\)表示第\(i\)个字母在原字符串\(S\)中出现的次数。
这个递推式的正确性在于,枚举第\(i\)个字母的使用数量\(k\),然后将其插入之前的\(j - k\)个字母中。

代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

typedef long long ll;

const int N = 5010, mod = 998244353;

int n;
char s[N];
ll cnt[30];

ll fact[N], infact[N];
ll f[30][N];

ll qmi(ll a, ll b)
{
    ll res = 1;
    while(b) {
        if(b & 1) res = res * a % mod;
        a = a * a % mod;
        b >>= 1;
    }
    return res;
}

void init()
{
    fact[0] = infact[0] = 1;
    for(int i = 1; i <= 5000; i ++) {
        fact[i] = fact[i - 1] * i % mod;
    }
    for(int i = 1; i <= 5000; i ++) {
        infact[i] = infact[i - 1] * qmi(i, mod - 2) % mod;
    }
}

ll C(ll a, ll b)
{
    return fact[a] * infact[a - b] % mod * infact[b] % mod;
}

int main()
{
    scanf("%s", s + 1);
    n = strlen(s + 1);
    init();
    for(int i = 1; i <= n; i ++) {
        int t = s[i] - 'a' + 1;
        cnt[t] ++;
    }
    for(int i = 0; i <= cnt[1]; i ++) {
        f[1][i] = 1;
    }
    for(int i = 2; i <= 26; i ++) {
        for(int j = 0; j <= n; j ++) {
            for(int k = 0; k <= min(j, (int)cnt[i]); k ++) {
                f[i][j] = (f[i][j] + f[i - 1][j - k] * C(j, k) % mod) % mod;
            }
        }
    }
    ll ans = 0;
    for(int i = 1; i <= n; i ++) ans = (ans + f[26][i]) % mod;
    printf("%lld\n", ans);
    return 0;
}
posted @ 2022-03-29 18:48  pbc的成长之路  阅读(33)  评论(0编辑  收藏  举报