Description
有 $n$ 个数,为 $1$ ~ $m$,依次将它们入栈,有 $m$ 个约束条件,每个约束条件形如 $f_i$ 不能晚于 $g_i$ 出栈,求合法的出栈序列数,答案模 $998244353$。
Solution
显得经典但有趣的题目。
考虑区间 DP,设 $f_{i, j}$ 表示区间 $[i, j]$ 内的数均已出栈的合法方案数。
考虑怎么转移,设 $k$ 在 $[l, k - 1]$ 出去后再出去 $[k + 1, r]$ 再出去 $k$ 是合法的,则有:
,其中 $A$ 表示的是当前最后进的点是 $l$ 或 $r$ 的方案数。
这样直接 DP,暴力判断是否符合每个约束条件的话,时间复杂度是 $O(n^3m)$ 的,考虑优化。
发现当 $k \leq f_i \leq r ∧ l \leq g_i < k$ 或者 $f_i = k ∧ k < g_i < r$ 时转移不合法。
于是我们考虑到把一个约束条件看成一个点 $(f_i, g_i)$,发现我们只需要判断矩形内有没有点就可以快速地判断转移合不合法了。
于是我们做一遍二维前缀和,就可以 $O(1)$ 地判断转移的合法性了。
时间复杂度 $O(n^2m)$
Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| #include <cstdio> #define mod 998244353 #define maxN 310 long long sum[maxN][maxN], f[maxN][maxN]; long long Sum (long long A, long long B, long long C, long long D) { return sum[C][D] - sum[A - 1][D] - sum[C][B - 1] + sum[A - 1][B - 1]; } int main () { long long n = 0, m = 0; scanf("%lld %lld", &n, &m); for(long long i = 1;i <= m; i++) { long long x = 0, y = 0; scanf("%lld %lld", &x, &y); sum[x][y]++; } for(long long i = 1;i <= n; i++) { for(long long j = 1;j <= n; j++) { sum[i][j] += sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1]; } } for(long long i = 1;i <= n; i++) { f[i][i] = 1; } for(long long len = 1;len <= n; len++) { const long long maxL = n - len + 1; for(long long l = 1;l <= maxL; l++) { long long r = l + len - 1; if(!Sum(l, l + 1, l, r)) { f[l][r] += f[l + 1][r]; f[l][r] %= mod; } if(!Sum(r, l, r, r - 1)) { f[l][r] += f[l][r - 1]; f[l][r] %= mod; } for(long long k = l + 1;k < r; k++) { if(!Sum(k, l, r, k - 1) && !Sum(k, k + 1, k, r)) { f[l][r] += f[l][k - 1] * f[k + 1][r] % mod; f[l][r] %= mod; } } } } printf("%lld", f[1][n]); return 0; }
|