前缀和优化DP

知周所众,\(DP\) 有一大堆杂七杂八的优化,例如线段树优化\(DP\)、斜率优化、单调队列优化等等等等一大坨。

本篇博客,就来讲一讲。

一般形式(可用范围)

对于和

当你的 \(DP\) 式子里有以下两种形式就可以使用。

形式1:

\[dp_i = \sum_{j=L_i}^{R_i} f(j) + val_i \]

其中,\(f(j)\) 表示只和 \(j\) 有关的数, \(val_i\) 表示任意数

形式2:

\[dp_{i,j} = \sum_{k=L}^R dp_{i - 1, k} \]

为什么这两种能用前缀和优化呢?

我们注意力惊人,一眼看出这里有重复的地方,想想前缀和的作用,可以发现前缀和足以优化掉最内层的一个循环。

对于最值

同上,只需要看要用前缀最值还是后缀最值。

例题

Distance Sequence

题目翻译:

有多少个长度为 \(N\) 的整数序列 \(A=(A_1,\ldots,A_N)\) 满足以下所有条件?

  • \(1\le A_i \le M\) \((1 \le i \le N)\)

  • \(|A_i - A_{i+1}| \geq K\) \((1 \le i \le N - 1)\)

由于计数可能很大,请对 \(998244353\) 求模。

做法

暴力做法

\(dp_{i,j}\) 表示到第 \(i\) 个小朋友,当前已经用掉 \(j\) 颗糖的方案数。

状态转移式为:

\[dp_{i,j}=\sum_{k=max(0,j-a_i)}^j dp_{i-1,k} \]

此时时间复杂度为 \(O(nk^2)\)

而这个刚好符合形式\(2\)的式子,所以可以前缀和优化。

我们不妨再开一个数组 \(sum\),表示\(dp\)的前缀和。

我们可以得到式子:

\[sum_i = \sum_{j=0}^i dp_{i-1,j} \]

那么转移式就为

\[dp_{i,j} = \sum_{k=\max(0,j-a_i)}^{j} (sum_j - sum_{\max(0,j-a_i)-1}) \]

但是要注意边界条件!

如果 \(j - a_i < 0\),那么 $ sum_{\max(0,j-a_i)-1} $ 应该取0

代码

记得取模和开long long

戳我喵~
const int mod = 1e9 + 7;

int n, m;
int a[N];
int dp[110][110000];
int cnt[110000];

main() {
	n = re, m = re;
	int sum = 0;
	for (int i = 1; i <= n; i++) cin >> a[i], dp[i][0] = 1, sum += a[i];
	if (sum < m) {
		puts("0");
		return 0;
	}
	dp[0][0] = 1;
	for (int i = 0; i <= m; i++) cnt[i] = 1;
	for (int i = 1; i <= n; i++) {
		dp[i][0] = cnt[0] = dp[i - 1][0];
		for (int j = 1; j <= m; j++) (dp[i][j] = dp[i][j] + (cnt[j] - (j - a[i] - 1 < 0 ? 0 : cnt[j - a[i] - 1]) + mod) % mod) %= mod;
		for (int j = 1; j <= m; j++) (cnt[j] = cnt[j - 1] + dp[i][j]) %= mod;
	}
	wr(dp[n][m] % mod), endl;
}
posted @ 2026-02-24 19:56  OiLight  阅读(24)  评论(0)    收藏  举报