题解:CF1988F Heartbeat

题目链接

首先显然前缀最大值和后缀最大值可以根据 \(n\) 的位置分成两段分开算,前后缀显然可以使用组合数简单合并。考虑 dp,设 \(dp_{i,j,k}\) 表示 \(i\) 阶排列,有 \(j\) 个上升点、\(k\) 个前缀最大值的方案数。然后有个特别巧的转移方法,每次插入排列的最小值,则可简单分类讨论解决。

我们设 \(f_{i,j}\) 为 \(i\) 阶排列有 \(j\) 个上升点的权值(要计算 \(a\)),\(g_{i,j}\) 表示 \(i\) 阶排列有 \(j\) 个上升点的权值(\(g\) 是算的后缀最大值,要考虑 \(b\),由于前缀最大值和后缀最大值本质是一回事,所以计算方法与 \(f\) 相同)。那么现在就相当于没有前缀最大值和后缀最大值的限制了可枚举 \(f,g\) 的 \(i,j\):\(ans_{i+j+1} \leftarrow f_{i,p} \times g_{j,q} \times c_{p+q+[i>0]} \times \binom{i+j}{j}\)。时间复杂度 \(O(n^4)\)。

考虑优化,首先 \(c_{p+q+[i>0]}\) 这个东西看着很烦,于是我们直接把 \(i=0\) 的情况拆出来单独算。然后我们可以预处理 \(val_{j,p}=\sum_{q} g_{j,q} \times c_{p+q+1}\),然后就可以 \(O(n^3)\) 统计答案了。

代码
#include <bits/stdc++.h>

using namespace std;

const int maxn = 705;
const int mod = 998244353;

int n, a[maxn], b[maxn], c[maxn];
int C[maxn][maxn], val[maxn][maxn], ans[maxn];
int dp[2][maxn][maxn], f[maxn][maxn], g[maxn][maxn];

inline void getadd(int &x, int y) {
	x += y;
	if(x >= mod) x -= mod;
}

int main() {
	cin >> n;
	for(int i = 1; i <= n; i++) cin >> a[i];
	for(int i = 1; i <= n; i++) cin >> b[i];
	for(int i = 0; i < n; i++) cin >> c[i];
	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]) % mod;
	}
	dp[1][0][1] = 1;
	f[0][0] = a[1], g[0][0] = b[1];
    f[1][0] = a[2], g[1][0] = b[2];
	for(int i = 2; i < n; i++) {
		int v = (i & 1), u = (v ^ 1);
		for(int j = 0; j <= i; j++)
			for(int k = 0; k <= i; k++)
				dp[v][j][k] = 0;
		for(int j = 0; j < i; j++) {
			for(int k = 0; k <= i; k++) {
				getadd(dp[v][j + 1][k + 1], dp[u][j][k]);
				getadd(dp[v][j][k], 1ll * (j + 1) * dp[u][j][k] % mod);
				getadd(dp[v][j + 1][k], 1ll * (i - 2 - j) * dp[u][j][k] % mod);
			}
		}
		for(int j = 0; j < i; j++) {
			for(int k = 0; k <= i; k++) {
				getadd(f[i][j], 1ll * a[k + 1] * dp[v][j][k] % mod);
				getadd(g[i][j], 1ll * b[k + 1] * dp[v][i - j - 1][k] % mod);
			}
		}
	}
	for(int j = 0; j < n; j++)
		for(int q = 0; q <= j; q++)
			getadd(ans[j + 1], 1ll * f[0][0] * g[j][q] % mod * c[q] % mod);
	for(int j = 0; j < n; j++)
		for(int p = 0; j + p < n; p++)
			for(int q = 0; q <= j; q++)
				getadd(val[j][p], 1ll * g[j][q] * c[p + q + 1] % mod);
	for(int i = 1; i < n; i++)
		for(int j = 0; i + j < n; j++)
			for(int p = 0; p <= i; p++)
				getadd(ans[i + j + 1], 1ll * f[i][p] * val[j][p] % mod * C[i + j][i] % mod);
	for(int i = 1; i <= n; i++) cout << ans[i] << " ";
	return 0;
}

总结: 这道题直接 dp 也可以设计出算法,但是时间复杂度和空间复杂度都过高,所以应考虑寻找题目性质,所以就应考虑按前后缀分开考虑。dp 转移时插入最小值的思路也十分巧妙,可以当作 trick 积累。最后统计答案时减小时间复杂度的方法也比较巧妙,可以先预处理一部分值然后在统计。

posted @ 2026-10-09 15:06  PrinceEvan  阅读(9)  评论(0)    收藏  举报