前缀和优化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;
}

浙公网安备 33010602011771号