Loading

loj#575. 「LibreOJ NOI Round #2」不等关系

记事件 \(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;
}
posted @ 2024-05-22 16:05  Chy12321  阅读(13)  评论(0编辑  收藏  举报