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;
}