有时会遇到这样的决策性单调问题,有一个二元函数 F ( x , y ) F(x,y) F ( x , y ) ,要对每个 x x x 求出 min F ( x , y ) \min F(x,y) min F ( x , y ) ,随着 x x x 增大,取到最小值的 y y y 也在增大,这样的问题可以用分治来解决,但 SMAWK 的复杂度更为优秀。
如果一个 N × M N\times M N × M 的矩阵 A A A 满足四边形不等式:
∀ i 1 < i 2 , j 1 < j 2 , A i 1 , j 1 + A i 2 , j 2 ≤ A i 1 , j 2 + A i 2 , j 1 \forall i_1<i_2,j_1<j_2,A_{i_1,j_1}+A_{i_2,j_2}\le A_{i_1,j_2}+A_{i_2,j_1}
∀ i 1 < i 2 , j 1 < j 2 , A i 1 , j 1 + A i 2 , j 2 ≤ A i 1 , j 2 + A i 2 , j 1
则称矩阵 A A A 是完全单调的。
SMAWK 算法解决的问题是求 A A A 每行的最小值,假设可以 O ( 1 ) O(1) O ( 1 ) 计算矩阵 A A A 中的一个元素,SMAWK 的复杂度为 O ( N + M ) O(N+M) O ( N + M ) 。
矩阵 A A A 的一个性质是决策单调性,即每行取到最小值的列编号随行编号递增。证明不困难,考虑
A i , j + A i + 1 , j + 1 ≤ A i , j + 1 + A i + 1 , j ⇒ A i , j + 1 − A i , j ≥ A i + 1 , j + 1 − A i + 1 , j A_{i,j}+A_{i+1,j+1}\le A_{i,j+1}+A_{i+1,j}\Rightarrow A_{i,j+1}-A_{i,j}\ge A_{i+1,j+1}-A_{i+1,j}
A i , j + A i + 1 , j + 1 ≤ A i , j + 1 + A i + 1 , j ⇒ A i , j + 1 − A i , j ≥ A i + 1 , j + 1 − A i + 1 , j
即 A ∗ , j A_{*,j} A ∗ , j 比 A ∗ , j + 1 A_{*,j+1} A ∗ , j + 1 随行数增加得更快,如果第 i i i 行的最小值在第 j j j 列取到,那么在 i i i 之后的行,j j j 之前的列都比第 j j j 列劣。
SWAWK 的核心操作是 reduce,可以去掉一些没用的列,使得剩余列数不超过行数。
reduce 操作维护一个存列编号的栈,它的性质为:对于栈内自底向上第 i i i 个元素 x x x 和第 i + 1 i+1 i + 1 个元素 y y y ,满足 A i , x < A i , y A_{i,x}< A_{i,y} A i , x < A i , y 。初始栈为空,然后从小到大插入每一列。假设当前要插入 c c c ,栈顶元素为 t o p top t o p ,栈的大小为 s i z e size s i z e ,如果 A s i z e , t o p ≥ A s i z e , c A_{size,top}\ge A_{size,c} A s i z e , t o p ≥ A s i z e , c ,那么可以弹掉 t o p top t o p ,原因:假设 t o p top t o p 在栈中的前一个元素为 x x x ,由于 A s i z e − 1 , x < A s i z e − 1 , t o p A_{size-1,x}< A_{size-1,top} A s i z e − 1 , x < A s i z e − 1 , t o p ,在第 1 → s i z e − 1 1\rightarrow size-1 1 → s i z e − 1 行中 t o p top t o p 没有 x x x 优秀,同理,在第 s i z e → n size\rightarrow n s i z e → n 行中 t o p top t o p 没有 c c c 优秀,故第 t o p top t o p 列是没用的。
弹掉没用的元素之后,如果当前栈的大小为 n n n ,说明此时 A n , t o p < A n , c A_{n,top}< A_{n,c} A n , t o p < A n , c ,元素 c c c 就不用插入了。否则就插入 c c c ,这不会破环栈的性质。最后栈内的剩余元素就是可能有用的列。
reduce 操作之后先求出偶数行的最小值和取到最小值的列,这是一个子问题,之后可以通过决策单调性 O ( n ) O(n) O ( n ) 求出奇数行的最小值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 int ans[MAXROW]; int A (int x, int y) { } void SMAWK (int k, const vector<int >& row, const vector<int >& col) { vector<int > Stack; for (int c : col) { while (!Stack.empty ()) { int r = Stack.size () - 1 ; if (A (r, c) > A (r, Stack.back ())) break ; Stack.pop_back (); } if (Stack.size () < row.size ()) Stack.push_back (c); } if (k < row.size ()) SMAWK (k << 1 , row, Stack); int r = k, c = 0 ; auto chkmin = [&]() { if (A (r, Stack[c]) < A (r, ans[r])) ans[r] = Stack[c]; }; while (r + k < row.size ()) { while (Stack[c] < ans[r + k]) chkmin (), c++; r += k << 1 ; } if (r < row.size ()) while (c < Stack.size ()) chkmin (), c++; }