字首和最佳化
這個最佳化的方法還是顯然的,就是當遇到形如 當前狀態是由先前的連續狀態轉移, 則考慮將這種 連續 用字首和維護,一般便會將這一部分的複雜度由 \(O(n)\) 降為 \(O(1)\)
對於我最近遇到的題來說是這樣思考的,當然,更重要的還是最佳化前的轉化,比如透過推式子形成可以字首和處理的求和式
[ABC179D] Leaping Tak
link
考慮定義 \(f[i]\) 表示走到第 \(i\) 個點的方案數
樸素地想,如果設給定的這些不重合區間的數的集合為 \(D\),則有很顯然的遞推式:
\[f[i] = \sum\limits_{j\in D}f[\max\{i - j, 0\}]
\]
但是這樣的複雜度是 \(O(n^2)\) 的,瓶頸在於每次遍歷集合 \(D\)
進一步,發現這樣算沒有用到 \(k\) 個區間 這個約束,同時 \(k\) 很小,考慮變形為:
\[f[i] = \sum\limits_{j = 1}^{k}\sum\limits_{p = L[j]}^{R[j]}f[\max\{i - p, 0\}]
\]
這就好了, 發現 \(\sum\limits_{p = L[j]}^{R[j]}f[\max\{i - p, 0\}]\) 是一段連續的 \(f\) 值區間,
啟發我們可以搞一個字首和陣列 \(s[i] = \sum\limits_{j=1}^{i}f[j]\),就像用字首和陣列求區間和一樣,顯然對於區間 \([L[j], R[j]]\) 的和就是 \(s[\max\{i - L[j], 0\}]-s[\max\{i - R[j] - 1\}]\)
注意 \(L[j] \leq R[j]\),所以 \(i - L[j]\) 會更大些在右邊,我一開始就搞反了
這樣就把裡邊那層求和式消掉了,複雜度降到 \(O(nk)\)
code
#include <bits/stdc++.h>
#define re register int
#define max(x, y) (x > y ? x : y)
using namespace std;
typedef long long LL;
const int N = 2e5 + 10, mod = 998244353;
int n, k, L[20], R[20];
LL f[N], s[N];
int main()
{
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cin >> n >> k;
for (re i = 1; i <= k; i ++) cin >> L[i] >> R[i];
s[1] = f[1] = 1;
for (re i = 2; i <= n; i ++)
for (re j = 1; j <= k; j ++)
{
f[i] = (f[i] + s[max(i - L[j], 0)] - s[max(i - R[j] - 1, 0)] + mod) % mod;
s[i] = (s[i - 1] + f[i]) % mod;
}
cout << f[n] % mod << '\n';
return 0;
}