[dp记录] ARC112E Cigar Box
对于每个数,它最终的位置取决于它的最后一次操作,记这样的操作为关键操作。
假设我们知道目标序列中,第一个“前移”的关键操作和第一个“后移”的关键操作的位置,记作 \(l,r\),可以确定,\([l,r]\)一定是没有操作过的,并且,关键“前移”操作的顺序为 \(l,l-1,...,1\),关键“后移”操作的顺序为 \(r,r+1,...,n\),\(a_l,...,a_r\) 满足单调递增。
于是可以设 \(dp(i,l,r)\) 表示做了 \(i\) 次操作,还有 \(l\) 个关键“前移”操作和 \(r\) 个关键“后移操作”的方案数,则:
\[dp(i,l,r) = dp(i-1,l,r) * 2 * (l + r) + dp(i-1,l+1,r) + dp(i-1,l,r+1)
\]
时间复杂度 \(O(n^2m)\)。
考虑优化这个 \(dp\),观察到 \(l,r\) 的顺序并没有要求,可以将其合并,改为 \(dp(i,j)\) 表示做了 \(i\) 次操作,还有 \(j\) 个关键操作的方案数,转移:
\[dp(i,j) \xrightarrow{\times 2j} dp(i+1,j)
\]
\[dp(i,j) \longrightarrow dp(i+1,j-1)
\]
则答案为 \(\dbinom {l+r} l dp(i,l+r)\),时间复杂度 \(O(nm)\)。
但是,这样的 \(dp\) 需要枚举 \(l+r\), \(n^2m\) 难以接受。
可以将 \(dp\) 反过来,求 \(dp(m,0)\) 对 \(dp(0,l+r)\) 的贡献,可以发现转移方程是类似的:\(dp(i,j) = dp(i+1,j)*2*j + dp(i+1,j-1)\)。
于是就可以做到 \(O(nm)\)。
\(\texttt{Code:}\)
#include <bits/stdc++.h>
using namespace std;
const int cmd = 998244353;
const int N = 3e3 + 5;
int n, m, a[N], dp[N][N], C[N][N];
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
dp[m][0] = 1;
for (int i = m - 1; ~i; i--)
for (int j = 0; j <= n; j++) {
dp[i][j] = 2ll * dp[i + 1][j] * j % cmd;
if (j) dp[i][j] = (dp[i][j] + dp[i + 1][j - 1]) % cmd;
}
for (int i = 0; i <= n; i++) {
C[i][0] = 1;
for (int j = 1; j <= i; j++)
C[i][j] = (C[i - 1][j] + C[i - 1][j - 1]) % cmd;
}
int ans = 0;
for (int i = 0; i <= n; i++) ans = (ans + 1ll * C[n][i] * dp[0][n]) % cmd;
for (int i = 1; i <= n; i++) {
ans = (ans + 1ll * C[n - 1][i - 1] * dp[0][n - 1]) % cmd;
for (int j = i + 1; j <= n; j++) {
if (a[j] < a[j - 1]) break;
ans = (ans + 1ll * C[n - (j - i + 1)][i - 1] * dp[0][n - (j - i + 1)]) % cmd;
}
}printf("%d", ans);
return 0;
}

浙公网安备 33010602011771号