弱化版:P13382 [GCJ 2011 Finals] Runs
考虑连续段 dp。为了方便处理,这里把字符串 离散化,记 表示 中最大的字符。
设 表示当前用了前 个字符,当前有恰好 个连续段的方案数。因为 串被离散化了,所以初始条件为 。
转移时考虑插入一个新的连续段(注意:不是单个字符),考虑将其插入对答案造成的影响:
- 插入到两个连续段之间:新增一个连续段
- 插入到一个连续段内:新增两个连续段
因此想到一个简单的转移:若当前用到第 个字符,当前恰有 个连续段,那么枚举插入到一个连续段内的连续段数 ,插入到两个连续段之间的连续段数 (需满足 且 ,其中 是原始串 的连续段数量),转移方程即为:
其中 为串 中字符 出现的次数, 为串 中字符 到字符 出现次数的总和( 为离散化后的串)。
直接暴力转移时间复杂度为 ,其中 为字符集大小,这里取 。
在 的 dp 做法上进行优化:直接做是困难的,所以可以想到去搞一些容斥之类的事。
改设 表示当前用到第前 个字符,当前最多有 个连续段的预留端点的方案数。
此时转移的时候枚举最后 字符形成了 个连续段,此时 个连续段会占用 个连续段的预留端点,因为其和顺序无关,所以方案数为 。
然后还需要把 个字符拆成 个连续段,这是经典问题,方案数为 。
最后剩余的 个分段点分给前 个字符,发现这个东西其实就是 的一个子问题,可以递归解决,方案数即为 。
显然可以通过加乘原理来完成整体的转移方程式:
算答案部分可以直接套用二项式反演公式,对于每个 ,答案为 。预处理阶乘和阶乘逆,因为有 所以总时间复杂度为 ,其中 取字符集大小,这里有 。
这个时间复杂度仍然无法通过,考虑继续优化。先优化 这个 dp 数组的求值过程。套路的拆组合数,得到:
此时我们惊喜的发现式子后面的红色部分是一个等和卷积,可以用 NTT 进行优化,在该部分做到时间复杂度 。
然后再考虑优化算答案部分。对于每个 ,答案的形式形如:
仍然是套路的拆组合数,得到:
后面红色部分的答案又是一个等和卷积的形式,同样可以使用 NTT 优化。因此本题做完了,总时间复杂度为 可以通过。
的代码(但是只能拿 分,经过一些卡常后可以拿 分):
// #pragma GCC optimize(3, "Ofast", "inline", "unroll-loops")
#include <iostream>
#include <string.h>
#include <bits/stl_algo.h>
#define int long long
using namespace std;
const int N = 1000010;
const int inf = 1e18;
const int mod = 998244353;
inline int power(int a, int b, int c)
{
int ans = 1;
while (b)
{
if (b & 1)
ans = ans * a % c;
a = a * a % c, b >>= 1;
}
return ans;
}
inline int inversion(int x) { return power(x, mod - 2, mod); }
using ull = unsigned long long;
using i128 = __int128;
int f[30][1010];
char s[N], t[N];
int fac[N], inv[N], ifac[N];
inline int binom(int a, int b)
{
if (b < 0 || a < b)
return 0;
return fac[a] * ifac[b] % mod * ifac[a - b] % mod;
}
inline void add(int &x, int v) { x += v; if (x >= mod) x -= mod; }
signed main()
{
// freopen("1.in", "r", stdin);
// freopen("1.out", "w", stdout);
// cin.tie(0)->sync_with_stdio(false);
for (int i = 0; i < 2; ++i)
fac[i] = inv[i] = ifac[i] = 1;
for (int i = 2; i < N; ++i)
{
fac[i] = fac[i - 1] * i % mod;
inv[i] = mod - inv[mod % i] * (mod / i) % mod;
ifac[i] = ifac[i - 1] * inv[i] % mod;
}
scanf("%s", s + 1);
int n = strlen(s + 1), buc[30] = {0};
for (int i = 1; i <= n; ++i)
t[i] = s[i];
sort(t + 1, t + n + 1);
int _ = unique(t + 1, t + n + 1) - t - 1;
for (int i = 1; i <= n; ++i)
s[i] = lower_bound(t + 1, t + _ + 1, s[i]) - t + 'a' - 1;
for (int i = 1; i <= n; ++i)
++buc[s[i] - 'a' + 1];
for (int m = 1; m <= n; ++m)
{
memset(f, 0, sizeof f);
int now = 0;
f[0][0] = 1;
for (int i = 1; i <= _; ++i)
{
for (int j = 0; j <= m; ++j)
{
f[i][j] = 0;
for (int k = 1; k <= min(j, buc[i]); ++k)
f[i][j] = (f[i][j] + binom(j, k) * binom(buc[i] - 1, k - 1) % mod * f[i - 1][j - k] % mod) % mod;
}
// cerr << "debug for " << i << ": ";
// for (int j = 0; j <= min(now, m); ++j)
// cerr << f[i][j] << ' ';
// cerr << '\n';
now += buc[i];
}
int sum = 0;
for (int i = 1; i <= m; ++i)
{
int coef = ((m - i) & 1) ? (mod - 1) : 1;
sum = (sum + coef * binom(n - i, m - i) % mod * f[_][i]) % mod;
}
cout << sum << ' ';
}
cout << '\n';
return 0;
}
的代码:
// #pragma GCC optimize(3, "Ofast", "inline", "unroll-loops")
#include <iostream>
#include <string.h>
#include <bits/stl_algo.h>
#define int long long
using namespace std;
const int N = 1000010;
const int inf = 1e18;
const int mod = 998244353;
inline int power(int a, int b, int c)
{
int ans = 1;
while (b)
{
if (b & 1)
ans = ans * a % c;
a = a * a % c, b >>= 1;
}
return ans;
}
inline int inversion(int x) { return power(x, mod - 2, mod); }
using ull = unsigned long long;
using i128 = __int128;
int f[27][100010];
char s[N], t[N];
int fac[N], inv[N], ifac[N];
inline int binom(int a, int b)
{
if (b < 0 || a < b)
return 0;
return fac[a] * ifac[b] % mod * ifac[a - b] % mod;
}
inline void add(int &x, int v) { x += v; if (x >= mod) x -= mod; }
namespace Poly
{
const int g = 3;
int rev[N];
void ntt(int *a, int n, int mode)
{
int bit = 1;
while ((1 << bit) < n)
++bit;
for (int i = 0; i < n; ++i)
{
rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (bit - 1));
if (i < rev[i])
swap(a[i], a[rev[i]]);
}
for (int l = 2; l <= n; l <<= 1)
{
int x = power(g, (mod - 1) / l, mod);
if (mode == 1)
x = inversion(x);
for (int i = 0; i < n; i += l)
{
int v = 1;
for (int j = 0; j < l / 2; ++j, v = v * x % mod)
{
int v1 = a[i + j], v2 = a[i + j + l / 2] * v % mod;
a[i + j] = (v1 + v2) % mod, a[i + j + l / 2] = (v1 - v2 + mod) % mod;
}
}
}
}
// calc convolution: c[i] = \sum\limits_{j=0}^i (a[j] * b[i - j])
void convolution(int *a, int n, int *b, int m, int *c)
{
int tn = n, tm = m;
n = n + m + 2;
while (__builtin_popcount(n) > 1)
++n;
for (int i = tn + 1; i <= n + 1; ++i)
a[i] = 0;
for (int i = tm + 1; i <= n + 1; ++i)
b[i] = 0;
ntt(a, n, 0), ntt(b, n, 0);
for (int i = 0; i < n; ++i)
c[i] = a[i] * b[i] % mod;
ntt(c, n, 1);
const int inv_n = inversion(n);
for (int i = 0; i <= n + m; ++i)
c[i] = c[i] * inv_n % mod;
}
}
int A[N], B[N], C[N];
signed main()
{
// freopen("1.in", "r", stdin);
// freopen("1.out", "w", stdout);
// cin.tie(0)->sync_with_stdio(false);
for (int i = 0; i < 2; ++i)
fac[i] = inv[i] = ifac[i] = 1;
for (int i = 2; i < N; ++i)
{
fac[i] = fac[i - 1] * i % mod;
inv[i] = mod - inv[mod % i] * (mod / i) % mod;
ifac[i] = ifac[i - 1] * inv[i] % mod;
}
scanf("%s", s + 1);
int n = strlen(s + 1), buc[30] = {0};
for (int i = 1; i <= n; ++i)
t[i] = s[i];
sort(t + 1, t + n + 1);
int _ = unique(t + 1, t + n + 1) - t - 1;
for (int i = 1; i <= n; ++i)
s[i] = lower_bound(t + 1, t + _ + 1, s[i]) - t + 'a' - 1;
for (int i = 1; i <= n; ++i)
++buc[s[i] - 'a' + 1];
memset(f, 0, sizeof f);
int now = 0;
f[0][0] = 1;
for (int i = 1; i <= _; ++i)
{
A[0] = 0;
for (int k = 1; k <= buc[i]; ++k)
A[k] = ifac[k] * ifac[k - 1] % mod * ifac[buc[i] - k] % mod;
for (int k = 0; k <= now; ++k)
B[k] = ifac[k] * f[i - 1][k] % mod;
Poly::convolution(A, buc[i], B, now, C);
for (int j = 0; j <= now + buc[i]; ++j)
f[i][j] = C[j] * fac[j] % mod * fac[buc[i] - 1] % mod;
now += buc[i];
}
memset(A, 0, sizeof A);
memset(B, 0, sizeof B);
for (int i = 0; i <= n; ++i)
A[i] = fac[n - i] * f[_][i] % mod;
for (int i = 0; i <= n; ++i)
{
int coef = (i & 1) ? (mod - 1) : 1;
B[i] = ifac[i] * coef % mod;
}
Poly::convolution(A, n, B, n, C);
for (int m = 1; m <= n; ++m)
cout << C[m] * ifac[n - m] % mod << ' ';
cout << '\n';
return 0;
}
喜欢的话,留下你的评论吧~