[dp记录] ARC112E Cigar Box

\(\texttt{link}\)

对于每个数,它最终的位置取决于它的最后一次操作,记这样的操作为关键操作

假设我们知道目标序列中,第一个“前移”的关键操作和第一个“后移”的关键操作的位置,记作 \(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;
}
posted @ 2021-11-03 19:03  klii  阅读(282)  评论(0)    收藏  举报