记事件 \(A\) 为「当 \(s_i = \texttt<\) 时 \(p_i < p_{i + 1}\)」,事件 \(B\) 为「当 \(s_i = \texttt<\) 时 \(p_i < p_{i+1}\),且存在 \(s_j = \texttt>\),满足 \(p_i < p_{i+1}\)。所求即 \(n(A) - n(B)\)。
\(n(A)\) 是好求的,相当于部分定序排列,记每个递增段的长度为 \(a_1, a_2, \dots, a_k\),则 \(n(A) = \dfrac{n!}{\prod_{i=1}^ka_i!}\)。
\(n(B)\) 可以容斥地求,设有 \(m\) 个 \(\texttt>\),\(E_i\) 表示第 \(i\) 个 \(\texttt>\) 满足 \(p_i < p_{i+1}\),则有 \(n(B) = \sum\limits_{j=0}^n(-1)^j\sum\limits_{i_1, i_2, \cdots, i_j}n(E_1 \cap E_2 \cap \dots \cap E_j)\)。
考虑用 DP 把前后两次容斥放到一起做。
发现 \(n(E_1 \cap E_2 \dots \cap E_j)\) 实际上就是特殊的 \(n(A)\)(有部分 \(\texttt>\) 被钦定为 \(\texttt<\)),所以 \(n(A)\) 和 \(n(B)\) 中都会有 \(n!\) 这个系数,所以显然最后的答案里也会有,不妨把 \(n!\) 提出来。
设 \(i! \cdot f(i)\) 表示长度为 \(n\) 的合法串数(带容斥系数),答案即 \(n!\cdot f(n)\)。
考虑转移,我们可以任选前面的任意一个 \(\texttt>\),并把它后面的所有 \(\texttt>\) 都钦定为 \(\texttt<\),于是产生了一个新的递增段,统计方案数的变化。
至于为什么不把当前选定的 \(\texttt>\) 也钦定为 \(\texttt<\),那是因为如若钦定,则原来它分割的两个递增段会连到一起,但我们并不知道前一个递增段的起点,所以无法转移了。
但是有一个遗漏的情况,即把所有的 \(\texttt>\) 都钦定为 \(\texttt<\)。把 \(s_0\) 也看作 \(\texttt>\) 然后正常转移即可。
于是写出转移方程(注意我们已经提了分子 \(n!\) 了,考虑分母即可):
\[f(i) = \begin{cases} 1 & i = 0 \\ \sum\limits_{j=0}^{i-1}[s_j \ne \texttt<](-1)^{cnt_{i-1} - cnt_j}\dfrac{f(j)}{(i-j)!} & i \ge 1\end{cases} \]其中 \(cnt_i\) 为 \(s_1, s_2, \dots s_i\) 中 \(\texttt>\) 数量。
时间复杂度 \(\mathcal O(n^2)\)。
提出一个 \((-1)^{cnt_{i-1}}\) 的系数来,设其为 \(p_i\),再令 \(sign_j = \begin{cases} (-1)^{cnt_j} & s_j \ne \texttt< \\ 0 & s_j = \texttt< \end{cases}\),则转移方程还可以写成:
\[f(i) = \begin{cases} 1 & i = 0 \\ p_i\sum\limits_{j=0}^{i-1}sign_jf(j)\dfrac1{(i-j)!} & i \ge 1 \end{cases} \]不难看出这是一个卷积形式,分治 NTT 优化即可,时间复杂度 \(\mathcal O(n \log^2 n)\)。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
constexpr int N = 1 << 18, MOD = 998244353;
string s; int n, sign[N], p[N];
ll fct[N], ifct[N], f[N], G[2][18];
ll qp(ll base, int e) {
ll res = 1;
while (e) {if (e & 1) res = res * base % MOD; base = base * base % MOD; e >>= 1;}
return res;
}
namespace Poly {
int len, bits, rev[N];
void NTT(ll *A, bool I = 0) {
for (int i = 0; i < len; i++) if (i < rev[i]) swap(A[i], A[rev[i]]);
for (int i = 1; i < len; i <<= 1) {
ll wn = G[I][__builtin_ctz(i)];
for (int j = 0; j < len; j += (i << 1)) {
ll w = 1;
for (int k = j; k < j + i; k++) {
ll t = w * A[k + i] % MOD;
A[k + i] = (A[k] - t + MOD) % MOD, A[k] = (A[k] + t) % MOD;
w = w * wn % MOD;
}
}
}
}
void INTT(ll *A) {
NTT(A, 1); ll ilen = qp(len, MOD - 2);
for (int i = 0; i < len; i++) A[i] = A[i] * ilen % MOD;
}
void mul(ll *A, ll *B, ll *res, int na, int nb) {
len = 1, bits = -1; while (len <= na + nb) len <<= 1, bits++;
fill(A + na + 1, A + len, 0), fill(B + nb + 1, B + len, 0);
for (int i = 0; i < len; i++) rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << bits);
NTT(A), NTT(B);
for (int i = 0; i < len; i++) res[i] = A[i] * B[i] % MOD;
INTT(res);
}
}
ll A[N], B[N];
void cdq(int l, int r) {
if (l == r) {f[l] = f[l] * p[l] % MOD; return;}
int mid = (l + r) >> 1; cdq(l, mid);
for (int i = l; i <= mid; i++) A[i - l] = f[i] * sign[i] % MOD;
for (int i = l; i <= r; i++) B[i - l] = ifct[i - l];
Poly::mul(A, B, A, mid - l, r - l);
for (int i = mid + 1; i <= r; i++) f[i] = (f[i] + A[i - l]) % MOD;
cdq(mid + 1, r);
}
int main() {
ios_base::sync_with_stdio(0); cin.tie(nullptr), cout.tie(nullptr);
fct[0] = fct[1] = ifct[0] = ifct[1] = 1;
for (int i = 2; i < N; i++) ifct[i] = (MOD - MOD / i) * ifct[MOD % i] % MOD;
for (int i = 2; i < N; i++) fct[i] = fct[i - 1] * i % MOD, ifct[i] = ifct[i] * ifct[i - 1] % MOD;
for (int i = 0; i < 18; i++) G[0][i] = qp(3, (MOD - 1) / (1 << (i + 1))), G[1][i] = qp(G[0][i], MOD - 2);
cin >> s; n = s.length() + 1; s = '\0' + s;
f[0] = sign[0] = p[0] = 1;
for (int i = 1; i <= n; i++) {
p[i] = sign[i - 1];
sign[i] = s[i] == '>' ? MOD - sign[i - 1] : sign[i - 1];
}
for (int i = 1; i <= n; i++) if (s[i] == '<') sign[i] = 0;
cdq(0, n);
cout << fct[n] * f[n] % MOD;
return 0;
}
标签:cap,NOI,LibreOJ,loj,sum,texttt,sign,int,cases
From: https://www.cnblogs.com/chy12321/p/18206488