动态规划进阶:转移方程优化技巧全解
-
- 一、优化的核心目标与前提
-
- [1.1 常见可优化的转移方程形式](#1.1 常见可优化的转移方程形式)
- [1.2 优化的前提](#1.2 优化的前提)
- 二、单调队列优化:区间最值的高效查询
-
- [2.1 适用场景](#2.1 适用场景)
- [2.2 核心思想](#2.2 核心思想)
- [2.3 案例:滑动窗口最大值的DP版本](#2.3 案例:滑动窗口最大值的DP版本)
- 三、斜率优化:线性函数的最值求解
-
- [3.1 适用场景](#3.1 适用场景)
- [3.2 核心思想](#3.2 核心思想)
- [3.3 案例:任务安排问题](#3.3 案例:任务安排问题)
- 四、前缀和优化:区间累加的快速计算
-
- [4.1 适用场景](#4.1 适用场景)
- [4.2 核心思想](#4.2 核心思想)
- [4.3 案例:最大子段和(带长度限制)](#4.3 案例:最大子段和(带长度限制))
- 五、状态压缩与滚动数组:空间维度的优化
-
- [5.1 适用场景](#5.1 适用场景)
- [5.2 核心思想](#5.2 核心思想)
- [5.3 案例:0/1背包的空间优化](#5.3 案例:0/1背包的空间优化)
- 六、矩阵快速幂:线性递推的加速
-
- [6.1 适用场景](#6.1 适用场景)
- [6.2 核心思想](#6.2 核心思想)
- [6.3 案例:斐波那契数列第n项](#6.3 案例:斐波那契数列第n项)
- 七、优化技巧的选择与总结
动态规划(DP)的核心是"定义状态+推导转移方程",但很多时候,基础的转移方程会导致时间复杂度偏高(如 O ( n 2 ) O(n^2) O(n2)),难以应对大规模输入,此时转移方程的优化就成为突破性能瓶颈的关键。
一、优化的核心目标与前提
在深入技巧之前,我们需要明确:转移方程优化的本质是减少冗余计算。当基础转移方程中存在"重复查询区间最值""线性函数最值""累加区间和"等操作时,就有优化的空间。
1.1 常见可优化的转移方程形式
-
区间最值型 : d p [ i ] = max j ∈ [ i − k , i − 1 ] ( d p [ j ] + w ( i , j ) ) dp[i] = \max_{j \in [i-k, i-1]} (dp[j] + w(i,j)) dp[i]=maxj∈[i−k,i−1](dp[j]+w(i,j))(如滑动窗口类DP);
-
线性函数型 : d p [ i ] = min j < i ( a [ i ] ⋅ b [ j ] + c [ j ] + d [ i ] ) dp[i] = \min_{j < i} (a[i] \cdot b[j] + c[j] + d[i]) dp[i]=minj<i(a[i]⋅b[j]+c[j]+d[i])(如任务安排问题);
-
区间累加型 : d p [ i ] = ∑ j = l [ i ] r [ i ] d p [ j ] + w [ i ] dp[i] = \sum_{j = l[i]}^{r[i]} dp[j] + w[i] dp[i]=∑j=l[i]r[i]dp[j]+w[i](如区间和依赖的DP);
-
线性递推型 : d p [ i ] = k 1 ⋅ d p [ i − 1 ] + k 2 ⋅ d p [ i − 2 ] + ⋯ + k m ⋅ d p [ i − m ] dp[i] = k_1 \cdot dp[i-1] + k_2 \cdot dp[i-2] + \dots + k_m \cdot dp[i-m] dp[i]=k1⋅dp[i−1]+k2⋅dp[i−2]+⋯+km⋅dp[i−m](如斐波那契数列的高阶扩展)。
1.2 优化的前提
- 能从转移方程中提取出"可复用的结构"(如单调关系、线性函数、区间和等);
- 优化后能减少状态转移的计算量(如从 O ( n ) O(n) O(n) per step降至 O ( 1 ) O(1) O(1)或 O ( log n ) O(\log n) O(logn))。
二、单调队列优化:区间最值的高效查询
单调队列优化适用于**转移方程中需要查询"滑动窗口内最值"**的场景,其核心是用一个"单调队列"维护候选状态 j j j,使每次查询最值的时间从 O ( n ) O(n) O(n)降至 O ( 1 ) O(1) O(1)。
2.1 适用场景
转移方程形如:
d p [ i ] = max j ∈ [ L ( i ) , R ( i ) ] ( d p [ j ] + w ( i , j ) ) dp[i] = \max_{j \in [L(i), R(i)]} (dp[j] + w(i,j)) dp[i]=maxj∈[L(i),R(i)](dp[j]+w(i,j))
其中 L ( i ) L(i) L(i)和 R ( i ) R(i) R(i)是关于 i i i的单调函数(如 L ( i ) = i − k L(i) = i - k L(i)=i−k, R ( i ) = i − 1 R(i) = i - 1 R(i)=i−1),即窗口范围随 i i i单调变化。
2.2 核心思想
- 维护单调队列 :队列中存储候选 j j j的索引,且对应的 d p [ j ] + w ( i , j ) dp[j] + w(i,j) dp[j]+w(i,j)保持单调(递增或递减);
- 窗口裁剪 :当 j j j超出窗口范围 [ L ( i ) , R ( i ) ] [L(i), R(i)] [L(i),R(i)]时,从队首移除;
- 高效更新 :新 j j j加入队列时,从队尾移除所有"比当前 j j j差"的候选(即 d p [ j ] + w ( i , j ) dp[j] + w(i,j) dp[j]+w(i,j)不优的),确保队列单调性。
2.3 案例:滑动窗口最大值的DP版本
问题 :给定数组 n u m s nums nums和窗口大小 k k k,求每个位置 i i i对应的窗口 [ i − k + 1 , i ] [i-k+1, i] [i−k+1,i]内的最大值(用DP思想实现)。
基础转移方程:
d p [ i ] = max j = i − k + 1 i n u m s [ j ] dp[i] = \max_{j = i-k+1}^i nums[j] dp[i]=maxj=i−k+1inums[j]( d p [ i ] dp[i] dp[i]表示以 i i i为结尾的窗口最大值)
优化思路:用单调队列存储窗口内元素的索引,队列保持元素值递减,队首即为当前窗口最大值。
java
public int[] maxSlidingWindow(int[] nums, int k) {
int n = nums.length;
int[] dp = new int[n - k + 1]; // 结果数组
Deque<Integer> deque = new ArrayDeque<>(); // 单调队列(存储索引)
for (int i = 0; i < n; i++) {
// 1. 移除窗口外的元素(索引 <= i - k)
while (!deque.isEmpty() && deque.peekFirst() <= i - k) {
deque.pollFirst();
}
// 2. 移除队尾比当前元素小的元素(它们不可能是最大值)
while (!deque.isEmpty() && nums[deque.peekLast()] <= nums[i]) {
deque.pollLast();
}
// 3. 加入当前元素索引
deque.offerLast(i);
// 4. 窗口形成后,记录最大值(队首元素)
if (i >= k - 1) {
dp[i - k + 1] = nums[deque.peekFirst()];
}
}
return dp;
}
复杂度分析 :每个元素入队和出队各一次,总时间 O ( n ) O(n) O(n),空间 O ( k ) O(k) O(k)(队列最大存储 k k k个元素)。
三、斜率优化:线性函数的最值求解
斜率优化适用于转移方程可转化为线性函数最值问题 的场景,通过将状态 j j j映射为直线,将"求 d p [ i ] dp[i] dp[i]"转化为"求直线在某点的最值",再用凸包或单调队列维护直线集合。
3.1 适用场景
转移方程形如:
d p [ i ] = min j < i ( a [ i ] ⋅ b [ j ] + c [ j ] + d [ i ] ) dp[i] = \min_{j < i} (a[i] \cdot b[j] + c[j] + d[i]) dp[i]=minj<i(a[i]⋅b[j]+c[j]+d[i])
其中 a [ i ] a[i] a[i]和 d [ i ] d[i] d[i]是仅与 i i i相关的函数, b [ j ] b[j] b[j]和 c [ j ] c[j] c[j]是仅与 j j j相关的函数。可改写为:
d p [ i ] = d [ i ] + min j < i ( b [ j ] ⋅ a [ i ] + c [ j ] ) dp[i] = d[i] + \min_{j < i} (b[j] \cdot a[i] + c[j]) dp[i]=d[i]+minj<i(b[j]⋅a[i]+c[j])
此时,每个 j j j对应一条直线 y = b [ j ] ⋅ x + c [ j ] y = b[j] \cdot x + c[j] y=b[j]⋅x+c[j], d p [ i ] dp[i] dp[i]即为 x = a [ i ] x = a[i] x=a[i]时所有直线的最小值(加 d [ i ] d[i] d[i])。
3.2 核心思想
- 直线映射 :将每个 j j j映射为直线 y = k j ⋅ x + b j y = k_j \cdot x + b_j y=kj⋅x+bj( k j = b [ j ] k_j = b[j] kj=b[j], b j = c [ j ] b_j = c[j] bj=c[j]);
- 维护有效直线集 :若直线 l 1 l_1 l1在所有 x x x上均不如 l 2 l_2 l2优,则 l 1 l_1 l1可被淘汰(凸包优化);
- 查询最值 :对于 x = a [ i ] x = a[i] x=a[i],在有效直线集中找到最优直线(如斜率单调时用单调队列,否则用二分查找)。
3.3 案例:任务安排问题
问题 : n n n个任务按顺序执行,每次启动机器需支付固定成本 S S S,第 i i i个任务的成本为 t [ i ] t[i] t[i],且成本随启动后时间累积(启动后第 k k k个任务的成本为 t [ i ] ⋅ k t[i] \cdot k t[i]⋅k)。求总最小成本。
基础转移方程:
d p [ i ] = min j < i ( d p [ j ] + S + ∑ k = j + 1 i t [ k ] ⋅ ( k − j ) ) dp[i] = \min_{j < i} (dp[j] + S + \sum_{k = j+1}^i t[k] \cdot (k - j)) dp[i]=minj<i(dp[j]+S+∑k=j+1it[k]⋅(k−j))
通过前缀和优化 ∑ \sum ∑项后,可转化为:
d p [ i ] = min j < i ( d p [ j ] − s u m T [ j ] ⋅ j ) + s u m T [ i ] ⋅ i + S − s u m T [ j ] dp[i] = \min_{j < i} (dp[j] - sumT[j] \cdot j) + sumT[i] \cdot i + S - sumT[j] dp[i]=minj<i(dp[j]−sumT[j]⋅j)+sumT[i]⋅i+S−sumT[j]
其中 s u m T [ i ] sumT[i] sumT[i]是 t t t的前缀和。
令 a [ i ] = s u m T [ i ] a[i] = sumT[i] a[i]=sumT[i], b [ j ] = j b[j] = j b[j]=j, c [ j ] = d p [ j ] − s u m T [ j ] ⋅ j − s u m T [ j ] c[j] = dp[j] - sumT[j] \cdot j - sumT[j] c[j]=dp[j]−sumT[j]⋅j−sumT[j],则:
d p [ i ] = a [ i ] ⋅ i + S + min j < i ( − b [ j ] ⋅ a [ i ] + c [ j ] ) dp[i] = a[i] \cdot i + S + \min_{j < i} ( -b[j] \cdot a[i] + c[j] ) dp[i]=a[i]⋅i+S+minj<i(−b[j]⋅a[i]+c[j])
符合线性函数形式,可通过斜率优化求解。
java
public long minCost(int[] t, int S) {
int n = t.length;
long[] sumT = new long[n + 1]; // 前缀和:sumT[i] = t[0]+...+t[i-1]
for (int i = 1; i <= n; i++) {
sumT[i] = sumT[i - 1] + t[i - 1];
}
long[] dp = new long[n + 1];
Deque<Line> deque = new ArrayDeque<>();
// 初始直线:j=0时,k=-b[0]=0,b=c[0]=dp[0] - sumT[0]*0 - sumT[0] = 0 - 0 - 0 = 0
deque.offerLast(new Line(0, 0));
for (int i = 1; i <= n; i++) {
long x = sumT[i]; // 当前x = a[i] = sumT[i]
// 1. 弹出队首无效直线(斜率递增时,前面的直线在x处已不是最优)
while (deque.size() >= 2) {
Line l1 = deque.pollFirst();
Line l2 = deque.peekFirst();
if (getY(l1, x) >= getY(l2, x)) {
// l1不如l2优,移除
} else {
deque.addFirst(l1); // 恢复l1,退出
break;
}
}
// 2. 计算当前dp[i]
Line best = deque.peekFirst();
dp[i] = x * i + S + getY(best, x);
// 3. 加入当前j=i对应的直线
long k = -i; // k_j = -b[j] = -j
long b = dp[i] - sumT[i] * i - sumT[i]; // c[j] = dp[j] - sumT[j]*j - sumT[j]
Line newLine = new Line(k, b);
// 4. 弹出队尾无效直线(确保队列斜率递增,且新直线更优)
while (deque.size() >= 2) {
Line l2 = deque.pollLast();
Line l1 = deque.peekLast();
// 若l1与l2的交点 >= l2与newLine的交点,则l2无效
if (intersectionX(l1, l2) >= intersectionX(l2, newLine)) {
// 移除l2
} else {
deque.addLast(l2); // 恢复l2,退出
break;
}
}
deque.addLast(newLine);
}
return dp[n];
}
// 直线类:y = k*x + b
static class Line {
long k, b;
Line(long k, long b) {
this.k = k;
this.b = b;
}
}
// 计算直线在x处的y值
static long getY(Line l, long x) {
return l.k * x + l.b;
}
// 计算两条直线的交点x坐标(分数形式避免精度问题,此处简化为double)
static double intersectionX(Line l1, Line l2) {
return (double) (l2.b - l1.b) / (l1.k - l2.k);
}
复杂度分析 :每个 i i i对应的直线入队和出队各一次,总时间 O ( n ) O(n) O(n),空间 O ( n ) O(n) O(n)。
四、前缀和优化:区间累加的快速计算
前缀和优化适用于转移方程中需要累加区间和 的场景,通过预处理前缀和数组,将 O ( n ) O(n) O(n)的区间累加转为 O ( 1 ) O(1) O(1)查询。
4.1 适用场景
转移方程形如:
d p [ i ] = ∑ j = l [ i ] r [ i ] d p [ j ] + w [ i ] dp[i] = \sum_{j = l[i]}^{r[i]} dp[j] + w[i] dp[i]=∑j=l[i]r[i]dp[j]+w[i]
其中 l [ i ] l[i] l[i]和 r [ i ] r[i] r[i]是 i i i的函数,区间和的计算是瓶颈。
4.2 核心思想
-
预处理前缀和 :定义 p r e [ i ] = ∑ j = 0 i d p [ j ] pre[i] = \sum_{j=0}^i dp[j] pre[i]=∑j=0idp[j],则区间和 ∑ j = l r d p [ j ] = p r e [ r ] − p r e [ l − 1 ] \sum_{j=l}^{r} dp[j] = pre[r] - pre[l-1] ∑j=lrdp[j]=pre[r]−pre[l−1];
-
替换转移方程 :用前缀和表达式替代原区间和,将每次转移的计算量从 O ( n ) O(n) O(n)降至 O ( 1 ) O(1) O(1)。
4.3 案例:最大子段和(带长度限制)
问题 :给定数组 n u m s nums nums和最大长度 k k k,求长度不超过 k k k的连续子数组的最大和。
基础转移方程:
d p [ i ] = max j = max ( 0 , i − k ) i − 1 ( d p [ j ] + n u m s [ i ] ) dp[i] = \max_{j = \max(0, i-k)}^{i-1} (dp[j] + nums[i]) dp[i]=maxj=max(0,i−k)i−1(dp[j]+nums[i])( d p [ i ] dp[i] dp[i]表示以 i i i结尾的最大和)
优化思路 :用前缀和 p r e [ i ] pre[i] pre[i]记录 d p [ 0.. i ] dp[0..i] dp[0..i]的和,但更优的是结合单调队列维护区间最大值(同时用前缀和优化累加)。
java
public int maxSubarraySumWithLimit(int[] nums, int k) {
int n = nums.length;
int[] dp = new int[n];
dp[0] = nums[0];
int maxSum = dp[0];
// 前缀和数组
int[] pre = new int[n];
pre[0] = dp[0];
for (int i = 1; i < n; i++) {
pre[i] = pre[i - 1] + dp[i];
}
Deque<Integer> deque = new ArrayDeque<>();
deque.offer(0);
for (int i = 1; i < n; i++) {
// 移除窗口外的j(j < i - k)
while (!deque.isEmpty() && deque.peekFirst() < i - k) {
deque.pollFirst();
}
// dp[i] = max(dp[j] for j in [i-k, i-1]) + nums[i]
// 用单调队列的队首获取max(dp[j])
dp[i] = nums[i] + (deque.isEmpty() ? 0 : dp[deque.peekFirst()]);
// 维护单调队列(递减)
while (!deque.isEmpty() && dp[i] >= dp[deque.peekLast()]) {
deque.pollLast();
}
deque.offer(i);
maxSum = Math.max(maxSum, dp[i]);
}
return maxSum;
}
复杂度分析 :前缀和优化后,区间和查询为 O ( 1 ) O(1) O(1),结合单调队列维护最值,总时间 O ( n ) O(n) O(n)。
五、状态压缩与滚动数组:空间维度的优化
状态压缩适用于转移方程中当前状态仅依赖有限个前驱状态的场景,通过减少DP数组的维度(如二维→一维),降低空间复杂度。
5.1 适用场景
- 二维DP中, d p [ i ] [ j ] dp[i][j] dp[i][j]仅依赖 d p [ i − 1 ] [ j ] dp[i-1][j] dp[i−1][j]和 d p [ i ] [ j − 1 ] dp[i][j-1] dp[i][j−1](如0/1背包、最长公共子序列);
- 高维DP中,状态仅依赖前 k k k层(如 d p [ i ] [ j ] [ k ] dp[i][j][k] dp[i][j][k]仅依赖 d p [ i − 1 ] [ j ] [ k ] dp[i-1][j][k] dp[i−1][j][k])。
5.2 核心思想
- 识别依赖关系:确定当前状态依赖的前驱状态范围(如仅前一行或前一列);
- 复用空间:用一维数组替代二维数组,通过"滚动"更新覆盖旧状态(注意遍历顺序,避免覆盖未使用的前驱状态)。
5.3 案例:0/1背包的空间优化
基础二维转移方程:
d p [ i ] [ j ] = max ( d p [ i − 1 ] [ j ] , d p [ i − 1 ] [ j − w [ i ] ] + v [ i ] ) dp[i][j] = \max(dp[i-1][j], dp[i-1][j - w[i]] + v[i]) dp[i][j]=max(dp[i−1][j],dp[i−1][j−w[i]]+v[i])(前 i i i个物品,容量 j j j的最大价值)
优化后一维转移方程:
d p [ j ] = max ( d p [ j ] , d p [ j − w [ i ] ] + v [ i ] ) dp[j] = \max(dp[j], dp[j - w[i]] + v[i]) dp[j]=max(dp[j],dp[j−w[i]]+v[i])(逆序遍历容量 j j j,避免重复使用同一物品)
java
public int knapsack(int[] w, int[] v, int C) {
int n = w.length;
int[] dp = new int[C + 1];
for (int i = 0; i < n; i++) {
// 逆序遍历容量,防止同一物品被多次选择
for (int j = C; j >= w[i]; j--) {
dp[j] = Math.max(dp[j], dp[j - w[i]] + v[i]);
}
}
return dp[C];
}
复杂度分析 :空间复杂度从 O ( n ⋅ C ) O(n \cdot C) O(n⋅C)降至 O ( C ) O(C) O(C),时间复杂度仍为 O ( n ⋅ C ) O(n \cdot C) O(n⋅C)。
六、矩阵快速幂:线性递推的加速
矩阵快速幂适用于线性递推关系 的DP问题(如斐波那契数列、线性 recurrence),当 n n n很大时(如 n ≤ 1 0 9 n \leq 10^9 n≤109),可将时间复杂度从 O ( n ) O(n) O(n)降至 O ( log n ) O(\log n) O(logn)。
6.1 适用场景
转移方程形如线性递推:
d p [ i ] = a 1 ⋅ d p [ i − 1 ] + a 2 ⋅ d p [ i − 2 ] + ⋯ + a k ⋅ d p [ i − k ] dp[i] = a_1 \cdot dp[i-1] + a_2 \cdot dp[i-2] + \dots + a_k \cdot dp[i-k] dp[i]=a1⋅dp[i−1]+a2⋅dp[i−2]+⋯+ak⋅dp[i−k]
可表示为矩阵乘法: [ d p [ i ] d p [ i − 1 ] ⋮ d p [ i − k + 1 ] ] = M ⋅ [ d p [ i − 1 ] d p [ i − 2 ] ⋮ d p [ i − k ] ] \begin{bmatrix} dp[i] \\ dp[i-1] \\ \vdots \\ dp[i-k+1] \end{bmatrix} = M \cdot \begin{bmatrix} dp[i-1] \\ dp[i-2] \\ \vdots \\ dp[i-k] \end{bmatrix} dp[i]dp[i−1]⋮dp[i−k+1] =M⋅ dp[i−1]dp[i−2]⋮dp[i−k] ,其中 M M M是转移矩阵。
6.2 核心思想
- 构建转移矩阵:将递推关系转化为矩阵乘法形式;
- 矩阵幂加速 :用快速幂计算转移矩阵的 n n n次幂,再与初始向量相乘,得到 d p [ n ] dp[n] dp[n]。
6.3 案例:斐波那契数列第n项
递推关系 : d p [ i ] = d p [ i − 1 ] + d p [ i − 2 ] dp[i] = dp[i-1] + dp[i-2] dp[i]=dp[i−1]+dp[i−2]( d p [ 0 ] = 0 dp[0]=0 dp[0]=0, d p [ 1 ] = 1 dp[1]=1 dp[1]=1)
矩阵形式 : [ d p [ i ] d p [ i − 1 ] ] = [ 1 1 1 0 ] ⋅ [ d p [ i − 1 ] d p [ i − 2 ] ] \begin{bmatrix} dp[i] \\ dp[i-1] \end{bmatrix} = \begin{bmatrix} 1 & 1 \\ 1 & 0 \end{bmatrix} \cdot \begin{bmatrix} dp[i-1] \\ dp[i-2] \end{bmatrix} [dp[i]dp[i−1]]=[1110]⋅[dp[i−1]dp[i−2]]
java
public int fib(int n) {
if (n <= 1) return n;
// 转移矩阵:[[1,1],[1,0]]
int[][] mat = {{1, 1}, {1, 0}};
// 计算mat^(n-1)
int[][] matPow = matrixPower(mat, n - 1);
// 初始向量[dp[1], dp[0]] = [1, 0]
return matPow[0][0] * 1 + matPow[0][1] * 0;
}
// 矩阵快速幂:计算mat^power
int[][] matrixPower(int[][] mat, int power) {
int n = mat.length;
// 初始化单位矩阵
int[][] result = new int[n][n];
for (int i = 0; i < n; i++) {
result[i][i] = 1;
}
while (power > 0) {
if (power % 2 == 1) {
result = matrixMultiply(result, mat);
}
mat = matrixMultiply(mat, mat);
power /= 2;
}
return result;
}
// 矩阵乘法:a * b
int[][] matrixMultiply(int[][] a, int[][] b) {
int n = a.length;
int[][] res = new int[n][n];
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
for (int k = 0; k < n; k++) {
res[i][j] += a[i][k] * b[k][j];
}
}
}
return res;
}
复杂度分析 :矩阵乘法时间为 O ( k 3 ) O(k^3) O(k3)( k k k为矩阵维度),快速幂迭代 O ( log n ) O(\log n) O(logn)次,总时间 O ( k 3 log n ) O(k^3 \log n) O(k3logn),适用于 n n n极大的场景。
七、优化技巧的选择与总结
优化技巧 | 适用场景 | 时间复杂度优化 | 核心工具/思想 |
---|---|---|---|
单调队列优化 | 区间最值查询(窗口单调) | O ( n 2 ) → O ( n ) O(n^2) \to O(n) O(n2)→O(n) | 单调队列维护候选集 |
斜率优化 | 线性函数最值( d p [ i ] = a [ i ] ⋅ b [ j ] + ... dp[i] = a[i] \cdot b[j] + \dots dp[i]=a[i]⋅b[j]+...) | O ( n 2 ) → O ( n ) O(n^2) \to O(n) O(n2)→O(n) | 凸包/单调队列维护直线集 |
前缀和优化 | 区间累加求和 | O ( n 2 ) → O ( n ) O(n^2) \to O(n) O(n2)→O(n) | 前缀和数组快速查询 |
状态压缩 | 状态依赖有限前驱 | 空间 O ( n 2 ) → O ( n ) O(n^2) \to O(n) O(n2)→O(n) | 滚动数组复用空间 |
矩阵快速幂 | 线性递推( n n n极大) | O ( n ) → O ( log n ) O(n) \to O(\log n) O(n)→O(logn) | 矩阵乘法与快速幂 |
选择策略
- 若转移方程涉及"区间最值"且窗口单调:优先用单调队列优化;
- 若转移方程可化为线性函数形式:用斜率优化;
- 若涉及"区间和累加":用前缀和优化;
- 若空间复杂度过高且状态依赖简单:用状态压缩;
- 若 n n n极大且是线性递推:用矩阵快速幂。
That's all, thanks for reading~~
觉得有用就
点个赞
、收进收藏
夹吧!关注
我,获取更多干货~