1. 程式人生 > 其它 >ABC246Ex 01? Queries 題解

ABC246Ex 01? Queries 題解

ABC246Ex 01? Queries Solution

目錄

更好的閱讀體驗戳此進入

這題的題解是從我寫的 Blog 中截出來的,可以直接去看原 Blog。

淺談 DDP 與 廣義矩陣乘法(戳此進入)

引入

例題 #1

題目 SP1043 GSS1 - Can you answer these queries I

題面:對於序列 $ a_n \(,\)

q $ 次詢問求 $ \left[ l_i, r_i \right] $ 的區間最大子段和。

$ 1 \le n \le 5 \times 10^4, a_i \le 15007 \(。\) q $ 在 int 範圍內。

有顯然的 DP,令 $ dp(i) $ 表示以 $ a_i $ 結尾的區間 $ \left[ 1, i \right] $ 的最大子段和。

一般的區間最大子段和轉移為 $ dp(i) = \max(dp(i - 1) + a_i, a_i) $,對於本題則為:

\[dp(i) = \left\{ \begin{array}{ll} a_i &\quad i = l \\ \max(dp(i - 1) + a_i, a_i) &\quad l \lt i \le r \end{array} \right. \]

但是每次詢問都重新維護一遍顯然會寄,所以考慮一些人類智慧。

廣義矩陣乘法

首先對於普通的矩陣乘法,我們隨便舉個例子:

\[\begin{bmatrix} a & b \\ c & d \end{bmatrix} \begin{bmatrix} e \\ f \end{bmatrix} = \begin{bmatrix} ae + bf \\ ce + df \end{bmatrix} \]

我們考慮把這玩意省略的東西寫全一點:

\[\begin{bmatrix} a & b \\ c & d \end{bmatrix} \begin{bmatrix} e \\ f \end{bmatrix} = \begin{bmatrix} a \times e + b \times f \\ c \times e + d \times f \end{bmatrix} \]

我們發現這裡一共有兩種符號,$ + $ 和 $ \times $,考慮替換這兩個符號,假設我們存在兩個二元運算子 $ \oplus $ 和 $ \otimes $,替換一下就是:

\[\begin{bmatrix} a & b \\ c & d \end{bmatrix} \begin{bmatrix} e \\ f \end{bmatrix} = \begin{bmatrix} a \otimes e \oplus b \otimes f \\ c \otimes e \oplus d \otimes f \end{bmatrix} \]

這就是廣義矩陣乘法了。

然後為了讓這東西能有點用,所以現在我們還需要讓廣義矩陣乘法具有結合律,也就是:

\[A(BC) = (AB)C \]

或者寫成:

\[(A(BC))_{i, j} = ((AB)C)_{i, j} \]

展開一下有:

\[\begin{aligned} (A(BC))_{i, j} &= \bigoplus_{k = 1}^{p} A_{i, p} \otimes (BC)_{p, j} \\ &= \bigoplus_{k = 1}^{p} \left( A_{i, p} \otimes \left( \bigoplus_{s = 1}^{q}B_{p, s} \otimes C_{s, j} \right) \right) \end{aligned} \] \[\begin{aligned} ((AB)C)_{i, j} &= \bigoplus_{s = 1}^{q} (AB)_{i, s} \otimes C_{s, j} \\ &= \bigoplus_{s = 1}^{q} \left( \left( \bigoplus_{k = 1}^{p}A_{i, k} \otimes B_{k, p} \right) \otimes C_{s, j} \right) \end{aligned} \]

不難發現,如果 $ \otimes $ 對 $ \oplus $ 存在分配律,或者說滿足 $ (a \oplus b) \otimes c = (a \otimes c) \oplus (b \otimes c) $,那麼把上面的兩個式子拆開之後,最後就會發現實際上是相同的。

所以我們便可得出這個結論——如果 $ \otimes $ 對 $ \oplus $ 存在分配律,那麼形成的廣義矩陣乘法矩陣之間就滿足結合律。

DDP

為了便於理解,我們先引入一個更簡單的例子。

例題 #0

題面:求斐波那契數列。

十分顯然的 $ dp(i) = dp(i - 1) + dp(i - 2) $。

我們考慮一些讓複雜度更高奇妙的方法。

我們考慮把這個轉移矩陣記作 $ X $,轉移的起始和終點分別寫成一個矩陣,為了湊齊矩陣,可以把終點塞一個 $ 0 $,或者 $ dp(i - 1) $ 這種無關的值,如替換為 $ 0 $,則有:

\[X \begin{bmatrix} dp(i - 1)\\ dp(i - 2) \end{bmatrix} = \begin{bmatrix} dp(i) \\ 0 \end{bmatrix} \]

此時的 $ \otimes $ 就是 $ \times \(,\) \oplus $ 就是 $ + $,也就是說我們這個矩陣之間的乘法就是經典的矩陣乘法,則不難算出我們需要的矩陣 $ X $ 為:

\[X = \begin{bmatrix} 1 & 1 \\ 0 & 0 \end{bmatrix} \]

也就是說:

\[\begin{bmatrix} 1 & 1 \\ 0 & 0 \end{bmatrix} \begin{bmatrix} dp(i - 1)\\ dp(i - 2) \end{bmatrix} = \begin{bmatrix} dp(i) \\ 0 \end{bmatrix} \]

於是我們不如繼續嘗試把 $ \texttt{LHS} $ 的帶有 $ dp $ 的式子接著拆下去:

\[X \begin{bmatrix} dp(i - 2)\\ dp(i - 3) \end{bmatrix} = \begin{bmatrix} dp(i - 1) \\ dp(i - 2) \end{bmatrix} \]

這東西雖然可以轉移的,但是自己觀察一下,這東西的形式和最開始的柿子好像形式很相似,於是我們考慮如果不去選擇 $ 0 $ 而去選擇 $ dp(i - 1) $ 可能會更方便一些(當然選擇 $ 0 $ 也是可以轉移的,只是會更復雜一些)。所以我們最開始的式子就變成了:

\[X \begin{bmatrix} dp(i - 1)\\ dp(i - 2) \end{bmatrix} = \begin{bmatrix} dp(i) \\ dp(i - 1) \end{bmatrix} \]

然後這樣也很簡單就能求出來 $ X $ 了:

\[\begin{bmatrix} 1 & 1 \\ 1 & 0 \end{bmatrix} \begin{bmatrix} dp(i - 1)\\ dp(i - 2) \end{bmatrix} = \begin{bmatrix} dp(i) \\ dp(i - 1) \end{bmatrix} \]

然後我們繼續往下轉移:

\[\begin{bmatrix} 1 & 1 \\ 1 & 0 \end{bmatrix} \begin{bmatrix} 1 & 1 \\ 1 & 0 \end{bmatrix} \begin{bmatrix} dp(i - 2)\\ dp(i - 3) \end{bmatrix} = \begin{bmatrix} dp(i) \\ dp(i - 1) \end{bmatrix} \]

此時我們便能發現一些奇妙的推導:

\[\begin{bmatrix} 1 & 1 \\ 1 & 0 \end{bmatrix}^{i - 3} \begin{bmatrix} dp(2)\\ dp(1) \end{bmatrix} = \begin{bmatrix} dp(i) \\ dp(i - 1) \end{bmatrix} \]

於是這個時候我們便可以矩陣快速冪(因此我們才需要使廣義矩陣之間滿足結合律,否則用不了矩陣快速冪或者線段樹貓樹之類的)把原來的線性求,變成現在的 $ \log $ 求了(雖然常數大了點)。

當然這只是最最基礎的 DDP。

例題 #0.5

題目:LG-P1115 最大子段和

最大欄位和模板題,因為比較水所以這裡就不敘述的太詳細了,令 $ dp(i) $ 表示以 $ a_i $ 結尾的區間 $ \left[ 1, i \right] $ 的最大子段和,有轉移 $ dp(i) = \max(dp(i - 1) + a_i, a_i) $,但是這樣求完是還需要再掃一遍取 $ \max $,這樣我們就不太好用矩陣轉移,於是考慮再令 $ ans(i) = \max(dp(i), ans(i - 1)) $。於是我們不難推出如下轉移:

\[\begin{bmatrix} ans(i - 1) & dp(i - 1) & 0 \end{bmatrix} \begin{bmatrix} 0 & -\infty & -\infty \\ a_i & a_i & -\infty \\ a_i & a_i & 0 \end{bmatrix} = \begin{bmatrix} ans(i) & dp(i) & 0 \end{bmatrix} \]

同時注意這裡的矩陣是廣義矩陣乘法,$ \otimes $ 為 $ + \(,\) \oplus $ 為 $ \max $,顯然 $ + $ 對 $ \max $ 有分配律,則矩陣有結合律。

然後像之前一樣拆下去就可以得到一大串矩陣相乘,成功大幅增加常數成功提高了轉移的擴充套件性。

例題 #1

現在讓我們回到文章伊始提到的例題,這裡我們再回顧一下:

題目 SP1043 GSS1 - Can you answer these queries I

題面:對於序列 $ a_n \(,\) q $ 次詢問求 $ \left[ l_i, r_i \right] $ 的區間最大子段和。

$ 1 \le n \le 5 \times 10^4, a_i \le 15007 \(。\) q $ 在 int 範圍內。

有顯然的 DP,令 $ dp(i) $ 表示以 $ a_i $ 結尾的區間 $ \left[ 1, i \right] $ 的最大子段和。

一般的區間最大子段和轉移為 $ dp(i) = \max(dp(i - 1) + a_i, a_i) $,對於本題則為:

\[dp(i) = \left\{ \begin{array}{ll} a_i &\quad i = l \\ \max(dp(i - 1) + a_i, a_i) &\quad l \lt i \le r \end{array} \right. \]

和上一題一樣,我們還是考慮令:

\[ans(i) = \left\{ \begin{array}{ll} dp(i) &\quad i = l \\ \max(ans(i - 1), dp(i)) &\quad l \lt i \le r \end{array} \right. \]

$ i = l $ 就是個特判可以不用考慮,考慮把 $ l \lt i \le r $ 的式子變成廣義矩乘:

\[\begin{bmatrix} ans(i - 1) & dp(i - 1) & 0 \end{bmatrix} \begin{bmatrix} 0 & -\infty & -\infty \\ a_i & a_i & -\infty \\ a_i & a_i & 0 \end{bmatrix} = \begin{bmatrix} ans(i) & dp(i) & 0 \end{bmatrix} \]

然後繼續往下拆:

\[\begin{bmatrix} ans(i - 2) & dp(i - 2) & 0 \end{bmatrix} \begin{bmatrix} 0 & -\infty & -\infty \\ a_{i - 1} & a_{i - 1} & -\infty \\ a_{i - 1} & a_{i - 1} & 0 \end{bmatrix} \begin{bmatrix} 0 & -\infty & -\infty \\ a_i & a_i & -\infty \\ a_i & a_i & 0 \end{bmatrix} = \begin{bmatrix} ans(i) & dp(i) & 0 \end{bmatrix} \]

一直拆到邊界,就是:

\[\begin{bmatrix} ans(l) & dp(l) & 0 \end{bmatrix} \begin{bmatrix} 0 & -\infty & -\infty \\ a_{l + 1} & a_{l + 1} & -\infty \\ a_{l + 1} & a_{l + 1} & 0 \end{bmatrix} \cdots \begin{bmatrix} 0 & -\infty & -\infty \\ a_{i - 1} & a_{i - 1} & -\infty \\ a_{i - 1} & a_{i - 1} & 0 \end{bmatrix} \begin{bmatrix} 0 & -\infty & -\infty \\ a_i & a_i & -\infty \\ a_i & a_i & 0 \end{bmatrix} = \begin{bmatrix} ans(i) & dp(i) & 0 \end{bmatrix} \]

因為我們要的答案為 $ ans(r) $,所以把 $ i $ 換成 $ r $。並且顯然有 $ ans(l) = dp(l) = a_l $,則原式化為:

\[\begin{bmatrix} a_l & a_l & 0 \end{bmatrix} \begin{bmatrix} 0 & -\infty & -\infty \\ a_{l + 1} & a_{l + 1} & -\infty \\ a_{l + 1} & a_{l + 1} & 0 \end{bmatrix} \cdots \begin{bmatrix} 0 & -\infty & -\infty \\ a_{r - 1} & a_{r - 1} & -\infty \\ a_{r - 1} & a_{r - 1} & 0 \end{bmatrix} \begin{bmatrix} 0 & -\infty & -\infty \\ a_r & a_r & -\infty \\ a_r & a_r & 0 \end{bmatrix} = \begin{bmatrix} ans(r) & dp(r) & 0 \end{bmatrix} \]

可以發現我們想要得到 $ ans(r) $,只需要從已知具體值的 $ \begin{bmatrix} a_l & a_l & 0 \end{bmatrix} $,乘上一大堆矩陣。

為了便於書寫,我們記:

\[A_i = \begin{bmatrix} 0 & -\infty & -\infty \\ a_i & a_i & -\infty \\ a_i & a_i & 0 \end{bmatrix} \]

則:

\[\begin{bmatrix} ans(r) & dp(r) & 0 \end{bmatrix} = \begin{bmatrix} a_l & a_l & 0 \end{bmatrix} A_{l + 1} A_{l + 2} \cdots A_{r - 1} A_r \]

而在實際使用中我們還有很多小技巧與轉化來讓我們實現起來更簡單

如對於矩陣我們一般通過新開一個結構體然後過載 * 實現,這個時候我們可以考慮把初始矩陣補齊成 $ 3 \times 3 $ 的,這樣最終答案也會變成 $ 3 \times 3 $,我們便只需要維護長寬均為 $ 3 $ 的定大小的矩陣即可,至於補齊的值是什麼這個實際上是任意的,因為我們不需要這些補齊的值算出來的答案,所以最終計算出什麼也是無關緊要的,計算後直接輸出答案矩陣的 $ (0, 0) $ 位置,也就是 $ ans(r) $ 即可。

舉個例子:

\[\begin{bmatrix} a_l & a_l & 0 \\ -\infty & -\infty & -\infty \\ -\infty & -\infty & -\infty \end{bmatrix} \]

然後在過載矩陣的乘法的時候,我們應該注意,是否有可能會超出 intlong long 等,所以在我們取 $ \max $ 的時候可以考慮額外加一個 $ -\infty $,也就是當有兩個數相加比 $ -\infty $ 更小的時候那麼其中一定至少有一個數為 $ -\infty $,這個時候我們直接將答案變成 $ -\infty $ 即可。

然後如果嚴格按照上面的演算法實現出來後,會發現一個問題,在某些特殊的時候答案會錯誤,經過 debug 我們發現當 $ l = r $ 的時候,本來應該只有最初的初始矩陣,但是按照我們的演算法會多出來一個 $ \left[ l + 1, r \right] $ 的查詢,但此時 $ l + 1 \gt r $,我們當然可以考慮特判這個情況,但是我們實際上還可以再次轉換一下思路,讓這個東西更泛用,更美觀。

我們考慮能否將初始矩陣 $ \begin{bmatrix}a_l & a_l & 0\end{bmatrix} $ 也寫成 $ A_l $,然後在最前面再弄一個新的初始矩陣 $ T $,讓它和 $ A_l $ 運算後會變成我們想要的 $ \begin{bmatrix}a_l & a_l & 0\end{bmatrix} $,於是很容易寫出:

\[T \begin{bmatrix} 0 & -\infty & -\infty \\ a_{l} & a_{l} & -\infty \\ a_{l} & a_{l} & 0 \end{bmatrix} = \begin{bmatrix} a_l & a_l & 0 \\ -\infty & -\infty & -\infty \\ -\infty & -\infty & -\infty \end{bmatrix} \]

不難算出:

\[T = \begin{bmatrix} -\infty & 0 & 0 \\ -\infty & -\infty & -\infty \\ -\infty & -\infty & -\infty \\ \end{bmatrix} \]

所以我們的式子就最終變成了:

\[\begin{bmatrix} ans(r) & dp(r) & 0 \\ -\infty & -\infty & -\infty \\ -\infty & -\infty & -\infty \end{bmatrix} = \begin{bmatrix} -\infty & 0 & 0 \\ -\infty & -\infty & -\infty \\ -\infty & -\infty & -\infty \\ \end{bmatrix} A_{l} A_{l + 1} \cdots A_{r - 1} A_r \]

現在這個式子就很好維護了。

這個時候我們觀察那一大串 $ A $,不難發現這玩意好像就是把 $ \left[ l, r \right] $ 的 $ A $ 乘到一起了,而且每次詢問會有不同的 $ l, r $,這不就是區間查詢嗎,我們於是可以考慮用一些妙妙資料結構去維護這玩意。

此時我們可能會想到樹狀陣列,但是仔細思考一下就會發現是不行的。眾所周知樹狀陣列在運算上有一個類似差分的過程,也就是說需要有逆元的存在,但是在我們定義的矩陣和矩陣之間的廣義矩乘構成的群中是否存在逆元呢?顯然我們都知道矩陣求逆的一個必要條件是行列式不為 $ 0 $,這東西是否不為 $ 0 $ 是無法確定的,也就是說不一定會存在逆元,所以不能用樹狀陣列或者字首積之類的東西區間查詢。

一個比較顯然的東西就是線段樹,每個節點存的是一個矩陣,或者說對應區間的矩陣乘起來(看到這裡大概就又能意識到讓矩陣滿足結合律的重要性了)。當然這裡的乘仍然指的是 $ \otimes $ 為 $ + \(,\) \oplus $ 為 $ \max $ 的廣義矩乘。用線段樹維護的話最後的複雜度大概是 $ O(\xi^3 (n + q)) $,其中 $ \xi $ 為矩陣的行數或列數,這裡 $ \xi = 3 $。

對於本題來說,我們可以發現是多次查詢沒有修改,所以也可以考慮使用貓樹,使查詢複雜度變為 $ O(1) $,這樣總複雜度就變成了 $ O(\xi^3(n \log n + q)) $,具體介紹可以看咕咕日報 一種神奇的資料結構——貓樹

當然用平衡樹,分塊之類的也能做,這裡不再過多贅述。

這裡提供一個 DDP + 貓樹 實現的 GSS1。(注意貓樹節點數需要補齊到 $ 2^t $,且要開好陣列範圍的大小,否則很容易 RE)

(篇幅原因這裡的程式碼省略了,可以從文章頭的連結到原 Blog 訪問檢視)

例題 #2

題目:[ABC246Ex] 01? Queries

題面:給定長度為 $ N $ 的僅包含 01? 的字串 $ S $,給定 $ Q $ 組詢問 $ (x_1, c_1), (x_2, c_2), \cdots, (x_q, c_q) $,每次將原字串中 $ x_i $ 位置的字元改為 $ c_i $,然後輸出 $ S $ 有多少種非空子串,? 需任意替換為 01

$ 1 \le N, Q \le 10^5, 1 \le x_i \le N $。

我們先不考慮修改,思考對於這樣一個字串能有多少種子串,顯然這個東西是個 DP。

令 $ dp(i, 0 / 1) $ 表示考慮前 $ i $ 位以 $ 0 $ 或 $ 1 $ 結尾的方案數,顯然有如下轉移:

若 $ S_i = 1 $,有:

\[dp(i, 0) = dp(i - 1, 0) \] \[dp(i, 1) = dp(i - 1, 0) + dp(i - 1, 1) + 1 \]

若 $ S_i = 0 $,有:

\[dp(i, 0) = dp(i - 1, 0) + dp(i - 1, 1) + 1 \] \[dp(i, 1) = dp(i - 1, 1) \]

若 $ S_i = \texttt{?} $,有:

\[dp(i, 0) = dp(i - 1, 0) + dp(i - 1, 1) + 1 \] \[dp(i, 1) = dp(i - 1, 0) + dp(i - 1, 1) + 1 \]

應該不難理解吧?如果狀態裡是當前這一位,那麼可以接到上一個狀態任意的結尾,接上這一位之後都會是一個符合要求的新串,或者丟棄以前的直接讓這一位成為一個新串。反之就直接由上一次的轉移而來,把這一位丟棄,而 $ \texttt{?} $ 可以認為是任意一個,所以可以接在任意一個的後面。

這裡有一個細節可以解釋一下,在這一位和狀態相同的時候我們會 $ +1 $,但是如果之前就有一個孤立的(這時很顯然會有的),比如 $ 0 $,那麼我們這一位又一個新的 $ 0 $ 難道不會重嗎?這顯然是不會的,因為上一個狀態的 $ 0 $ 在這一次的討論中已經變成 $ 00 $ 了,只有在討論到上界的時候才會存在真正的孤立的 $ 0 $ 或 $ 1 $,於是顯然可以保證不重不漏。

這麼一大坨分類討論顯然很難維護,那麼我們嘗試把這些縮到一起:

\[dp(i, 0) = dp(i - 1, 0) + \left[ S_i \neq 1 \right](dp(i - 1, 1) + 1) \] \[dp(i, 1) = dp(i - 1, 1) + \left[ S_i \neq 0 \right](dp(i - 1, 0) + 1) \]

關於式子中的方括號括起來的表示式,這個一般用來表示如果裡面的表示式為 $ \texttt{true} $,那麼這個東西值為 $ 1 $,反之為 $ 0 $。

現在,我們就可以對這個簡潔的式子搞事情了,因為我們後面要有很多次修改,這樣的話顯然可以考慮 DDP。

我們再次嘗試設計出轉移的矩陣:

\[\begin{bmatrix} dp(i - 1, 0) & dp(i - 1, 1) \\ 0 & 0 \end{bmatrix} T = \begin{bmatrix} dp(i, 0) & dp(i, 1) \\ 0 & 0 \end{bmatrix} \]

不難算出:不難算出我們算不出來。

於是考慮再加一維

\[\begin{bmatrix} dp(i - 1, 0) & dp(i - 1, 1) & 1 \\ 0 & 0 & 0 \\ 0 & 0 & 0 \end{bmatrix} T = \begin{bmatrix} dp(i, 0) & dp(i, 1) & 1\\ 0 & 0 & 0 \\ 0 & 0 & 0 \end{bmatrix} \]

然後嘗試推一下 $ T $,這東西個人認為實際上就是個蒙猜湊,總之最後弄一下就能出來這個(注意這裡我們用的是普通矩乘規則,非廣義):

\[\begin{bmatrix} dp(i - 1, 0) & dp(i - 1, 1) & 1 \\ 0 & 0 & 0 \\ 0 & 0 & 0 \end{bmatrix} \begin{bmatrix} 1 & \left[ S_i \neq 0 \right] & 0 \\ \left[ S_i \neq 1 \right] & 1 & 0 \\ \left[ S_i \neq 1 \right] & \left[ S_i \neq 0 \right] & 1 \end{bmatrix} = \begin{bmatrix} dp(i, 0) & dp(i, 1) & 1 \\ 0 & 0 & 0 \\ 0 & 0 & 0 \end{bmatrix} \]

(不要問我為什麼這題用不到廣義矩乘還要在前面說一大堆廣義矩乘的內容,我記錯了,技多不壓身)

然後類比之前的過程,令:

\[A_i = \begin{bmatrix} 1 & \left[ S_i \neq 0 \right] & 0 \\ \left[ S_i \neq 1 \right] & 1 & 0 \\ \left[ S_i \neq 1 \right] & \left[ S_i \neq 0 \right] & 1 \end{bmatrix} \]

那麼一直拆下去,一定有:

\[\begin{bmatrix} dp(1, 0) & dp(1, 1) & 1 \\ 0 & 0 & 0 \\ 0 & 0 & 0 \end{bmatrix} A_2 A_3 \cdots A_n = \begin{bmatrix} dp(n, 0) & dp(n, 1) & 1 \\ 0 & 0 & 0 \\ 0 & 0 & 0 \end{bmatrix} \]

依然嘗試找到一個這樣的等式:

\[\begin{bmatrix} 0 & 0 & 1 \\ 0 & 0 & 0 \\ 0 & 0 & 0 \end{bmatrix} \begin{bmatrix} 1 & \left[ S_1 \neq 0 \right] & 0 \\ \left[ S_1 \neq 1 \right] & 1 & 0 \\ \left[ S_1 \neq 1 \right] & \left[ S_1 \neq 0 \right] & 1 \end{bmatrix} = \begin{bmatrix} dp(n, 0) & dp(n, 1) & 1 \\ 0 & 0 & 0 \\ 0 & 0 & 0 \end{bmatrix} \]

所以可以最終化為:

\[\begin{bmatrix} 0 & 0 & 1 \\ 0 & 0 & 0 \\ 0 & 0 & 0 \end{bmatrix} A_1 A_2 \cdots A_n = \begin{bmatrix} dp(n, 0) & dp(n, 1) & 1 \\ 0 & 0 & 0 \\ 0 & 0 & 0 \end{bmatrix} \]

終於推完了,不難發現我們每次的修改就是 $ A_i $ 的值,然後查詢整個區間。

這玩意有修改顯然用不了貓樹了,老老實實寫線段樹吧。。

複雜度大概是 $ O(\xi^3(n + q \log n)) $,其中 $ \xi = 3 $。

#define _USE_MATH_DEFINES
#include <bits/extc++.h>

#define PI M_PI
#define E M_E
#define npt nullptr
#define SON i->to
#define OPNEW void* operator new(size_t)
#define ROPNEW(arr) void* Edge::operator new(size_t){static Edge* P = arr; return P++;}

using namespace std;
using namespace __gnu_pbds;

mt19937 rnd(random_device{}());
int rndd(int l, int r){return rnd() % (r - l + 1) + l;}
bool rnddd(int x){return rndd(1, 100) <= x;}

typedef unsigned int uint;
typedef unsigned long long unll;
typedef long long ll;
typedef long double ld;

#define MAXN (int)(1e5 + 100)
#define MOD (int)(998244353)

template< typename T = int >
inline T read(void);

int N, Q;
int S[MAXN];

struct Matrix3{
    int val[3][3];
    Matrix3(int v00, int v01, int v02, int v10, int v11, int v12, int v20, int v21, int v22):
        val{
            {v00, v01, v02},
            {v10, v11, v12},
            {v20, v21, v22}
        }{;}
    Matrix3(int S):
        val{
            {1, S != 0, 0},
            {S != 1, 1, 0},
            {S != 1, S != 0, 1}
        }{;}
    Matrix3(int val[][3]){for(int i = 0; i <= 2; ++i)for(int j = 0; j <= 2; ++j)this->val[i][j] = val[i][j];}
    Matrix3(void) = default;
    friend const Matrix3 operator * (const Matrix3 &x, const Matrix3 &y){
        int val[3][3]; memset(val, 0, sizeof val);
        for(int i = 0; i <= 2; ++i)for(int j = 0; j <= 2; ++j)for(int p = 0; p <= 2; ++p)
            val[i][j] = ((ll)val[i][j] + (ll)x.val[i][p] * y.val[p][j] % MOD) % MOD;
        return Matrix3(val);
    }
    void Print(void){
        for(int i = 0; i <= 2; ++i)for(int j = 0; j <= 2; ++j)
            printf("%d%c", val[i][j], j == 2 ? '\n' : ' ');
    }
}mt[MAXN];

class SegTree{
private:
    Matrix3 tr[MAXN << 2];
    #define LS (p << 1)
    #define RS (LS | 1)
    #define MID ((gl + gr) >> 1)
public:
    void Pushup(int p){tr[p] = tr[LS] * tr[RS];}
    void Build(int p = 1, int gl = 1, int gr = N){
        if(gl == gr)return tr[p] = mt[gl = gr], void();
        Build(LS, gl, MID);
        Build(RS, MID + 1, gr);
        Pushup(p);
    }
    void Modify(int idx, Matrix3 v, int p = 1, int gl = 1, int gr = N){
        if(gl == gr)return tr[p] = v, void();
        if(idx <= MID)Modify(idx, v, LS, gl, MID);
        else Modify(idx, v, RS, MID + 1, gr);
        Pushup(p);
    }
    Matrix3 Query(void){return tr[1];}
}st;

int main(){
    N = read(), Q = read();
    string s; cin >> s;
    for(int i = 1; i <= (int)s.size(); ++i)
        S[i] = s.at(i - 1) == '?' ? -1 : int(s.at(i - 1) - '0'),
        mt[i] = Matrix3(S[i]);
    st.Build();
    Matrix3 origin(0, 0, 1, 0, 0, 0, 0, 0, 0);
    while(Q--){
        int p = read();
        char c = getchar(); while(c != '0' && c != '1' && c != '?')c = getchar();
        int flag = c == '?' ? -1 : int(c - '0');
        st.Modify(p, Matrix3(flag));
        auto ans = origin * st.Query();
        printf("%d\n", (int)((ll)(ans.val[0][0] + ans.val[0][1]) % MOD));
    }
    fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
    return 0;
}

template < typename T >
inline T read(void){
    T ret(0);
    short flag(1);
    char c = getchar();
    while(c != '-' && !isdigit(c))c = getchar();
    if(c == '-')flag = -1, c = getchar();
    while(isdigit(c)){
        ret *= 10;
        ret += int(c - '0');
        c = getchar();
    }
    ret *= flag;
    return ret;
}

例題 #3

後面對於 DDP 更詳盡的講解可以 戳此進入

UPD

update-2022_10_22 初稿